diff --git a/datahandlers/catalogdb.cpp b/datahandlers/catalogdb.cpp index ef8187c0a..81c09ccc6 100644 --- a/datahandlers/catalogdb.cpp +++ b/datahandlers/catalogdb.cpp @@ -1,984 +1,984 @@ /*************************************************************************** catalogDB.cpp - K Desktop Planetarium ------------------- begin : 2012/03/08 copyright : (C) 2012 by Rishab Arora email : ra.rishab@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. * * * ***************************************************************************/ #include "catalogdb.h" #include "catalogdata.h" #include "catalogentrydata.h" #include "kstars/version.h" #include "../kstars/auxiliary/kspaths.h" #include "starobject.h" #include "deepskyobject.h" #include "skycomponent.h" #include #include #include #include bool CatalogDB::Initialize() { skydb_ = QSqlDatabase::addDatabase("QSQLITE", "skydb"); QString dbfile = KSPaths::locate(QStandardPaths::GenericDataLocation, QString("skycomponents.sqlite")); if (dbfile.isEmpty()) dbfile = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QString("skycomponents.sqlite"); QFile testdb(dbfile); bool first_run = false; if (!testdb.exists()) { qCWarning(KSTARS_CATALOG) << "DSO DB does not exist!"; first_run = true; } skydb_.setDatabaseName(dbfile); if (!skydb_.open()) { qCWarning(KSTARS_CATALOG) << i18n("Unable to open DSO database file!"); qCWarning(KSTARS_CATALOG) << LastError(); } else { qCDebug(KSTARS_CATALOG) << "Opened the DSO Database. Ready!"; if (first_run == true) { FirstRun(); } } skydb_.close(); return true; } void CatalogDB::FirstRun() { qCWarning(KSTARS_CATALOG) << i18n("Rebuilding Additional Sky Catalog Database"); QVector tables; tables.append("CREATE TABLE Version (" "Version CHAR DEFAULT NULL)"); tables.append("INSERT INTO Version VALUES (\"" KSTARS_VERSION "\")"); tables.append("CREATE TABLE ObjectDesignation (" "id INTEGER NOT NULL DEFAULT NULL PRIMARY KEY," "id_Catalog INTEGER DEFAULT NULL REFERENCES Catalog (id)," "UID_DSO INTEGER DEFAULT NULL REFERENCES DSO (UID)," "LongName MEDIUMTEXT DEFAULT NULL," "IDNumber INTEGER DEFAULT NULL," "Trixel INTEGER NULL)"); // TODO(kstar): `Trixel` int(11) NOT NULL COMMENT 'Trixel Number' // For Future safety tables.append("CREATE TABLE Catalog (" "id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT," "Name CHAR NOT NULL DEFAULT 'NULL'," "Prefix CHAR DEFAULT 'NULL'," "Color CHAR DEFAULT '#CC0000'," "Epoch FLOAT DEFAULT 2000.0," "Author CHAR DEFAULT NULL," "License MEDIUMTEXT DEFAULT NULL," "FluxFreq CHAR DEFAULT 'NULL'," "FluxUnit CHAR DEFAULT 'NULL')"); tables.append("CREATE TABLE DSO (" "UID INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT," "RA DOUBLE NOT NULL DEFAULT 0.0," "Dec DOUBLE DEFAULT 0.0," //"RA CHAR NOT NULL DEFAULT 'NULL'," //"Dec CHAR NOT NULL DEFAULT 'NULL'," "Type INTEGER DEFAULT NULL," "Magnitude DECIMAL DEFAULT NULL," "PositionAngle INTEGER DEFAULT NULL," "MajorAxis FLOAT NOT NULL DEFAULT NULL," "MinorAxis FLOAT DEFAULT NULL," "Flux FLOAT DEFAULT NULL," "Add1 VARCHAR DEFAULT NULL," "Add2 INTEGER DEFAULT NULL," "Add3 INTEGER DEFAULT NULL," "Add4 INTEGER DEFAULT NULL)"); for (int i = 0; i < tables.count(); ++i) { QSqlQuery query(skydb_); if (!query.exec(tables[i])) { qCWarning(KSTARS_CATALOG) << query.lastError(); } } return; } CatalogDB::~CatalogDB() { skydb_.close(); } QSqlError CatalogDB::LastError() { // error description is in QSqlError::text() return skydb_.lastError(); } QStringList *CatalogDB::Catalogs() { RefreshCatalogList(); return &catalog_list_; } void CatalogDB::RefreshCatalogList() { catalog_list_.clear(); skydb_.open(); - QSqlTableModel catalog(0, skydb_); + QSqlTableModel catalog(nullptr, skydb_); catalog.setTable("Catalog"); catalog.setSort(0, Qt::AscendingOrder); catalog.select(); for (int i = 0; i < catalog.rowCount(); ++i) { QSqlRecord record = catalog.record(i); QString name = record.value("Name").toString(); catalog_list_.append(name); // QString author = record.value("Author").toString(); // QString license = record.value("License").toString(); // QString compiled_by = record.value("CompiledBy").toString(); // QString prefix = record.value("Prefix").toString(); } catalog.clear(); skydb_.close(); } int CatalogDB::FindCatalog(const QString &catalog_name) { skydb_.open(); - QSqlTableModel catalog(0, skydb_); + QSqlTableModel catalog(nullptr, skydb_); catalog.setTable("Catalog"); catalog.setFilter("Name LIKE \'" + catalog_name + "\'"); catalog.select(); int catalog_count = catalog.rowCount(); QSqlRecord record = catalog.record(0); int returnval = -1; if (catalog_count > 0) returnval = record.value("id").toInt(); catalog.clear(); skydb_.close(); return returnval; } void CatalogDB::AddCatalog(const CatalogData &catalog_data) { skydb_.open(); - QSqlTableModel cat_entry(0, skydb_); + QSqlTableModel cat_entry(nullptr, skydb_); cat_entry.setTable("Catalog"); int row = 0; cat_entry.insertRows(row, 1); // row(0) is autoincerement ID cat_entry.setData(cat_entry.index(row, 1), catalog_data.catalog_name); cat_entry.setData(cat_entry.index(row, 2), catalog_data.prefix); cat_entry.setData(cat_entry.index(row, 3), catalog_data.color); cat_entry.setData(cat_entry.index(row, 4), catalog_data.epoch); cat_entry.setData(cat_entry.index(row, 5), catalog_data.author); cat_entry.setData(cat_entry.index(row, 6), catalog_data.license); cat_entry.setData(cat_entry.index(row, 7), catalog_data.fluxfreq); cat_entry.setData(cat_entry.index(row, 8), catalog_data.fluxunit); cat_entry.submitAll(); cat_entry.clear(); skydb_.close(); } void CatalogDB::RemoveCatalog(const QString &catalog_name) { // Part 1 Clear DSO Entries ClearDSOEntries(FindCatalog(catalog_name)); skydb_.open(); - QSqlTableModel catalog(0, skydb_); + QSqlTableModel catalog(nullptr, skydb_); // Part 2 Clear Catalog Table catalog.setTable("Catalog"); catalog.setFilter("Name LIKE \'" + catalog_name + "\'"); catalog.select(); catalog.removeRows(0, catalog.rowCount()); catalog.submitAll(); catalog.clear(); skydb_.close(); RefreshCatalogList(); } void CatalogDB::ClearDSOEntries(int catalog_id) { skydb_.open(); QStringList del_query; // FIXME(spacetime): Only delete from DSO if removed from all Designations // del_query.append("DELETE FROM DSO WHERE UID IN (SELECT UID_DSO FROM " // "ObjectDesignation WHERE id_Catalog = " + // QString::number(catalog_id) + ")"); del_query.append("DELETE FROM ObjectDesignation WHERE id_Catalog = " + QString::number(catalog_id)); for (int i = 0; i < del_query.count(); ++i) { QSqlQuery query(skydb_); if (!query.exec(del_query[i])) { qCWarning(KSTARS_CATALOG) << query.lastError(); } } skydb_.close(); } int CatalogDB::FindFuzzyEntry(const double ra, const double dec, const double magnitude) { /* * FIXME (spacetime): Match the incoming entry with the ones from the db * with certain fuzz. If found, store it in rowuid * This Fuzz has not been established after due discussion */ //skydb_.open(); - QSqlTableModel dsoentries(0, skydb_); + QSqlTableModel dsoentries(nullptr, skydb_); QString filter = "((RA - " + QString().setNum(ra) + ") between -0.0016 and 0.0016) and " "((Dec - " + QString().setNum(dec) + ") between -0.0016 and 0.0016) and" "((Magnitude - " + QString().setNum(magnitude) + ") between -0.1 and 0.1)"; // qDebug() << filter; dsoentries.setTable("DSO"); dsoentries.setFilter(filter); dsoentries.select(); int entry_count = dsoentries.rowCount(); QSqlRecord record = dsoentries.record(0); int returnval = -1; if (entry_count > 0) returnval = record.value("UID").toInt(); dsoentries.clear(); //skydb_.close(); // qDebug() << returnval; return returnval; } bool CatalogDB::AddEntry(const CatalogEntryData &catalog_entry, int catid) { if (!skydb_.open()) { qCWarning(KSTARS_CATALOG) << "Failed to open database to add catalog entry!"; qCWarning(KSTARS_CATALOG) << LastError(); return false; } bool retVal = _AddEntry(catalog_entry, catid); skydb_.close(); return retVal; } bool CatalogDB::_AddEntry(const CatalogEntryData &catalog_entry, int catid) { // Verification step // If RA, Dec are Null, it denotes an invalid object and should not be written if (catid < 0) { qCWarning(KSTARS_CATALOG) << "Catalog ID " << catid << " is invalid! Cannot add object."; return false; } if (catalog_entry.ra == KSParser::EBROKEN_DOUBLE || catalog_entry.ra == 0.0 || std::isnan(catalog_entry.ra) || catalog_entry.dec == KSParser::EBROKEN_DOUBLE || catalog_entry.dec == 0.0 || std::isnan(catalog_entry.dec)) { qCWarning(KSTARS_CATALOG) << "Attempt to add incorrect ra & dec with ID:" << catalog_entry.ID << " Long Name: " << catalog_entry.long_name; return false; } // Part 1: Adding in DSO table // I will not use QSQLTableModel as I need to execute a query to find // out the lastInsertId // Part 2: Fuzzy Match or Create New Entry int rowuid = FindFuzzyEntry(catalog_entry.ra, catalog_entry.dec, catalog_entry.magnitude); //skydb_.open(); if (rowuid == -1) //i.e. No fuzzy match found. Proceed to add new entry { QSqlQuery add_query(skydb_); add_query.prepare("INSERT INTO DSO (RA, Dec, Type, Magnitude, PositionAngle," " MajorAxis, MinorAxis, Flux) VALUES (:RA, :Dec, :Type," " :Magnitude, :PositionAngle, :MajorAxis, :MinorAxis," " :Flux)"); add_query.bindValue(":RA", catalog_entry.ra); add_query.bindValue(":Dec", catalog_entry.dec); add_query.bindValue(":Type", catalog_entry.type); add_query.bindValue(":Magnitude", catalog_entry.magnitude); add_query.bindValue(":PositionAngle", catalog_entry.position_angle); add_query.bindValue(":MajorAxis", catalog_entry.major_axis); add_query.bindValue(":MinorAxis", catalog_entry.minor_axis); add_query.bindValue(":Flux", catalog_entry.flux); if (!add_query.exec()) { qCWarning(KSTARS_CATALOG) << "Custom Catalog Insert Query FAILED!"; qCWarning(KSTARS_CATALOG) << add_query.lastQuery(); qCWarning(KSTARS_CATALOG) << add_query.lastError(); } // Find UID of the Row just added rowuid = add_query.lastInsertId().toInt(); add_query.clear(); } int ID = catalog_entry.ID; /* TODO(spacetime) * Possible Bugs in QSQL Db with SQLite * 1) Unless the db is closed and opened again, the next queries * fail. * 2) unless I clear the resources, db close fails. The doc says * this is to be rarely used. */ // Find ID of catalog //skydb_.close(); //catid = FindCatalog(catalog_entry.catalog_name); // Part 3: Add in Object Designation //skydb_.open(); QSqlQuery add_od(skydb_); if (ID >= 0) { add_od.prepare("INSERT INTO ObjectDesignation (id_Catalog, UID_DSO, LongName" ", IDNumber) VALUES (:catid, :rowuid, :longname, :id)"); add_od.bindValue(":id", ID); } else { //qWarning() << "FIXME: This query has not been tested!!!!"; add_od.prepare("INSERT INTO ObjectDesignation (id_Catalog, UID_DSO, LongName" ", IDNumber) VALUES (:catid, :rowuid, :longname," "(SELECT MAX(ISNULL(IDNumber,1))+1 FROM ObjectDesignation WHERE id_Catalog = :catid) )"); } add_od.bindValue(":catid", catid); add_od.bindValue(":rowuid", rowuid); add_od.bindValue(":longname", catalog_entry.long_name); bool retVal = true; if (!add_od.exec()) { qWarning() << "Query exec failed:"; qWarning() << add_od.lastQuery(); qWarning() << skydb_.lastError(); retVal = false; } add_od.clear(); //skydb_.close(); return retVal; } QString CatalogDB::GetCatalogName(const QString &fname) { QDir::setCurrent(QDir::homePath()); // for files with relative path QString filename = fname; // If the filename begins with "~", replace the "~" with the user's home // directory (otherwise, the file will not successfully open) if (filename.at(0) == '~') filename = QDir::homePath() + filename.mid(1, filename.length()); QFile ccFile(filename); if (ccFile.open(QIODevice::ReadOnly)) { QString catalog_name; QTextStream stream(&ccFile); QString line; for (int times = 10; times >= 0 && !stream.atEnd(); --times) { line = stream.readLine(); int iname = line.indexOf("# Name: "); if (iname == 0) { // line contains catalog name iname = line.indexOf(":") + 2; catalog_name = line.mid(iname); return catalog_name; } } } return QString(); } bool CatalogDB::AddCatalogContents(const QString &fname) { QDir::setCurrent(QDir::homePath()); // for files with relative path QString filename = fname; // If the filename begins with "~", replace the "~" with the user's home // directory (otherwise, the file will not successfully open) if (filename.at(0) == '~') filename = QDir::homePath() + filename.mid(1, filename.length()); QFile ccFile(filename); if (ccFile.open(QIODevice::ReadOnly)) { QStringList columns; // list of data column descriptors in the header QString catalog_name; char delimiter; QTextStream stream(&ccFile); // TODO(spacetime) : Decide appropriate number of lines to be read QStringList lines; for (int times = 10; times >= 0 && !stream.atEnd(); --times) lines.append(stream.readLine()); /*WAS * = stream.readAll().split('\n', QString::SkipEmptyParts); * Memory Hog! */ if (lines.size() < 1 || !ParseCatalogInfoToDB(lines, columns, catalog_name, delimiter)) { qWarning() << "Issue in catalog file header: " << filename; ccFile.close(); return false; } ccFile.close(); // The entry in the Catalog table is now ready! /* * Now 'Columns' should be a StringList of the Header contents * Hence, we 1) Convert the Columns to a KSParser compatible format * 2) Use KSParser to read stuff and store in DB */ // Part 1) Conversion to KSParser compatible format QList> sequence = buildParserSequence(columns); // Part 2) Read file and store into DB KSParser catalog_text_parser(filename, '#', sequence, delimiter); int catid = FindCatalog(catalog_name); skydb_.open(); skydb_.transaction(); QHash row_content; while (catalog_text_parser.HasNextRow()) { row_content = catalog_text_parser.ReadNextRow(); CatalogEntryData catalog_entry; dms read_ra(row_content["RA"].toString(), false); dms read_dec(row_content["Dc"].toString(), true); //qDebug()< assume space if (delimiter == '\0') delimiter = ' '; if (!foundDataColumns) { if (showerrs) errs.append(i18n("Parsing header: ") + i18n("No valid column descriptors found. Exiting")); return false; } if (i == lines.size()) { if (showerrs) errs.append(i18n("Parsing header: ") + i18n("No data lines found after" " header. Exiting.")); return false; } else { // Make sure Name, Prefix, Color and Epoch were set if (catalog_name.isEmpty()) { if (showerrs) errs.append(i18n("Parsing header: ") + i18n("No Catalog Name specified;" " setting to \"Custom\"")); catalog_name = i18n("Custom"); } if (catPrefix.isEmpty()) { if (showerrs) errs.append(i18n("Parsing header: ") + i18n("No Catalog Prefix specified" "; setting to \"CC\"")); catPrefix = "CC"; } if (catColor.isEmpty()) { if (showerrs) errs.append(i18n("Parsing header: ") + i18n("No Catalog Color specified" "; setting to Red")); catColor = "#CC0000"; } if (catEpoch == 0.) { if (showerrs) errs.append(i18n("Parsing header: ") + i18n("No Catalog Epoch specified" "; assuming 2000.")); catEpoch = 2000.; } #ifndef KSTARS_LITE // Detect a duplicate catalog name if (FindCatalog(catalog_name) != -1) { - if (KMessageBox::warningYesNo(0, + if (KMessageBox::warningYesNo(nullptr, i18n("A catalog of the same name already exists. " "Overwrite contents? If you press yes, the" " new catalog will erase the old one!"), i18n("Overwrite Existing Catalog")) == KMessageBox::No) { return false; } else { RemoveCatalog(catalog_name); } } #endif // Everything OK. Make a new Catalog entry in DB CatalogData new_catalog; new_catalog.catalog_name = catalog_name; new_catalog.prefix = catPrefix; new_catalog.color = catColor; new_catalog.epoch = catEpoch; new_catalog.fluxfreq = catFluxFreq; new_catalog.fluxunit = catFluxUnit; AddCatalog(new_catalog); return true; } } void CatalogDB::GetCatalogData(const QString &catalog_name, CatalogData &load_catalog) { skydb_.open(); - QSqlTableModel catalog(0, skydb_); + QSqlTableModel catalog(nullptr, skydb_); catalog.setTable("Catalog"); catalog.setFilter("Name LIKE \'" + catalog_name + "\'"); catalog.select(); QSqlRecord record = catalog.record(0); load_catalog.prefix = record.value("Prefix").toString(); load_catalog.color = record.value("Color").toString(); load_catalog.fluxfreq = record.value("FluxFreq").toString(); load_catalog.fluxunit = record.value("FluxUnit").toString(); load_catalog.epoch = record.value("Epoch").toFloat(); catalog.clear(); skydb_.close(); } void CatalogDB::GetAllObjects(const QString &catalog, QList &sky_list, QList> &object_names, CatalogComponent *catalog_ptr, bool includeCatalogDesignation) { qDeleteAll(sky_list); sky_list.clear(); QString selected_catalog = QString::number(FindCatalog(catalog)); skydb_.open(); QSqlQuery get_query(skydb_); get_query.prepare("SELECT Epoch, Type, RA, Dec, Magnitude, Prefix, " "IDNumber, LongName, MajorAxis, MinorAxis, " "PositionAngle, Flux FROM ObjectDesignation JOIN DSO " "JOIN Catalog WHERE Catalog.id = :catID AND " "ObjectDesignation.id_Catalog = Catalog.id AND " "ObjectDesignation.UID_DSO = DSO.UID"); get_query.bindValue(":catID", selected_catalog); // qWarning() << get_query.lastQuery(); // qWarning() << get_query.lastError(); // qWarning() << FindCatalog(catalog); if (!get_query.exec()) { qWarning() << get_query.lastQuery(); qWarning() << get_query.lastError(); } while (get_query.next()) { int cat_epoch = get_query.value(0).toInt(); unsigned char iType = get_query.value(1).toInt(); dms RA(get_query.value(2).toDouble()); dms Dec(get_query.value(3).toDouble()); float mag = get_query.value(4).toFloat(); QString catPrefix = get_query.value(5).toString(); int id_number_in_catalog = get_query.value(6).toInt(); QString lname = get_query.value(7).toString(); float a = get_query.value(8).toFloat(); float b = get_query.value(9).toFloat(); float PA = get_query.value(10).toFloat(); float flux = get_query.value(11).toFloat(); QString name; if (!includeCatalogDesignation && !lname.isEmpty()) { name = lname; lname = QString(); } else name = catPrefix + ' ' + QString::number(id_number_in_catalog); SkyPoint t; t.set(RA, Dec); if (cat_epoch == 1950) { // Assume B1950 epoch t.B1950ToJ2000(); // t.ra() and t.dec() are now J2000.0 // coordinates } else if (cat_epoch == 2000) { // Do nothing { } } else { // FIXME: What should we do? // FIXME: This warning will be printed for each line in the // catalog rather than once for the entire catalog qWarning() << "Unknown epoch while dealing with custom " "catalog. Will ignore the epoch and assume" " J2000.0"; } RA = t.ra(); Dec = t.dec(); // FIXME: It is a bad idea to create objects in one class // (using new) and delete them in another! The objects created // here are usually deleted by CatalogComponent! See // CatalogComponent::loadData for more information! if (iType == 0) // Add a star { StarObject *o = new StarObject(RA, Dec, mag, lname); sky_list.append(o); } else // Add a deep-sky object { DeepSkyObject *o = new DeepSkyObject(iType, RA, Dec, mag, name, QString(), lname, catPrefix, a, b, -PA); o->setFlux(flux); o->setCustomCatalog(catalog_ptr); sky_list.append(o); // Add name to the list of object names if (!name.isEmpty()) { object_names.append(qMakePair(iType, name)); } } if (!lname.isEmpty() && lname != name) { object_names.append(qMakePair(iType, lname)); } } get_query.clear(); skydb_.close(); } QList> CatalogDB::buildParserSequence(const QStringList &Columns) { QList> sequence; QStringList::const_iterator iter = Columns.begin(); while (iter != Columns.end()) { // Available Types: ID RA Dc Tp Nm Mg Flux Mj Mn PA Ig KSParser::DataTypes current_type; if (*iter == QString("ID")) current_type = KSParser::D_QSTRING; else if (*iter == QString("RA")) current_type = KSParser::D_QSTRING; else if (*iter == QString("Dc")) current_type = KSParser::D_QSTRING; else if (*iter == QString("Tp")) current_type = KSParser::D_INT; else if (*iter == QString("Nm")) current_type = KSParser::D_QSTRING; else if (*iter == QString("Mg")) current_type = KSParser::D_FLOAT; else if (*iter == QString("Flux")) current_type = KSParser::D_FLOAT; else if (*iter == QString("Mj")) current_type = KSParser::D_FLOAT; else if (*iter == QString("Mn")) current_type = KSParser::D_FLOAT; else if (*iter == QString("PA")) current_type = KSParser::D_FLOAT; else if (*iter == QString("Ig")) current_type = KSParser::D_SKIP; sequence.append(qMakePair(*iter, current_type)); ++iter; } return sequence; } diff --git a/kstars/auxiliary/QRoundProgressBar.h b/kstars/auxiliary/QRoundProgressBar.h index 96efd4bc2..3fd5042e5 100644 --- a/kstars/auxiliary/QRoundProgressBar.h +++ b/kstars/auxiliary/QRoundProgressBar.h @@ -1,252 +1,252 @@ /* * QRoundProgressBar - a circular progress bar Qt widget. * * Sintegrial Technologies (c) 2015-now * * The software is freeware and is distributed "as is" with the complete source codes. * Anybody is free to use it in any software projects, either commercial or non-commercial. * Please do not remove this copyright message and remain the name of the author unchanged. * * It is very appreciated if you produce some feedback to us case you are going to use * the software. * * Please send your questions, suggestions, and information about found issues to the * * sintegrial@gmail.com * */ #ifndef QROUNDPROGRESSBAR_H #define QROUNDPROGRESSBAR_H #include /** * @brief The QRoundProgressBar class represents a circular progress bar and maintains its API * similar to the *QProgressBar*. * * ### Styles * QRoundProgressBar currently supports Donut, Pie and Line styles. See setBarStyle() for more details. * * ### Colors * Generally QRoundProgressBar uses its palette and font attributes to define how it will look. * * The following \a QPalette members are considered: * - *QPalette::Window* background of the whole widget (normally should be set to Qt::NoBrush) * - *QPalette::Base* background of the non-filled progress bar area (should be set to Qt::NoBrush to make it transparent) * - *QPalette::AlternateBase* background of the central circle where the text is shown (for \a Donut style) * - *QPalette::Shadow* foreground of the non-filled progress bar area (i.e. border color) * - *QPalette::Highlight* background of the filled progress bar area * - *QPalette::Text* color of the text shown in the center * * Create a \a QPalette with given attributes and apply it via `setPalette()`. * * ### Color gradient * \a Donut and \a Pie styles allow to use color gradient for currernt value area instead of plain brush fill. * See setDataColors() for more details. * * ### Value text * Value text is generally drawn inside the QRoundProgressBar using its `font()` and \a QPalette::Text role from its `palette()`. * * To define pattern of the text, use setFormat() function (see Qt's \a QProgressBar for more details). * * To define number of decimals to be shown, use setDecimals() function. * * ### Font * To use own font for value text, apply it via `setFont()`. * * By default, font size will be adjusted automatically to fit the inner circle of the widget. */ class QRoundProgressBar : public QWidget { Q_OBJECT public: - explicit QRoundProgressBar(QWidget *parent = 0); + explicit QRoundProgressBar(QWidget *parent = nullptr); static const int PositionLeft = 180; static const int PositionTop = 90; static const int PositionRight = 0; static const int PositionBottom = -90; /** * @brief Return position (in degrees) of minimum value. * \sa setNullPosition */ double nullPosition() const { return m_nullPosition; } /** * @brief Defines position of minimum value. * @param position position on the circle (in degrees) of minimum value * \sa nullPosition */ void setNullPosition(double position); /** * @brief The BarStyle enum defines general look of the progress bar. */ enum BarStyle { /// Donut style (filled torus around the text) StyleDonut, /// Pie style (filled pie segment with the text in center) StylePie, /// Line style (thin round line around the text) StyleLine }; /** * @brief Sets visual style of the widget. * \sa barStyle */ void setBarStyle(BarStyle style); /** * @brief Returns current progree bar style. * \sa setBarStyle */ BarStyle barStyle() const { return m_barStyle; } /** * @brief Sets width of the outline circle pen. * @param penWidth width of the outline circle pen (in pixels) */ void setOutlinePenWidth(double penWidth); /** * @brief Returns width of the outline circle pen. */ double outlinePenWidth() const { return m_outlinePenWidth; } /** * @brief Sets width of the data circle pen. * @param penWidth width of the data circle pen (in pixels) */ void setDataPenWidth(double penWidth); /** * @brief Returns width of the data circle pen. */ double dataPenWidth() const { return m_dataPenWidth; } /** * @brief Sets colors of the visible data and makes gradient brush from them. * Gradient colors can be set for \a Donut and \a Pie styles (see setBarStyle() function). * * *Warning*: this function will override widget's `palette()` to set dynamically created gradient brush. * * @param stopPoints List of colors (should have at least 2 values, see Qt's \a QGradientStops for more details). * Color value at point 0 corresponds to the minimum() value, while color value at point 1 * corresponds to the maximum(). Other colors will be distributed accordingly to the defined ranges (see setRange()). */ void setDataColors(const QGradientStops &stopPoints); /** * @brief Defines the string used to generate the current text. * If no format is set, no text will be shown. * @param format see \a QProgressBar's format description * \sa setDecimals */ void setFormat(const QString &format); /** * @brief Sets format string to empty string. No text will be shown therefore. * See setFormat() for more information. */ void resetFormat(); /** * @brief Returns the string used to generate the current text. */ QString format() const { return m_format; } /** * @brief Sets number of decimals to show after the comma (default is 1). * \sa setFormat */ void setDecimals(int count); /** * @brief Returns number of decimals to show after the comma (default is 1). * \sa setFormat, setDecimals */ int decimals() const { return m_decimals; } /** * @brief Returns current value shown on the widget. * \sa setValue() */ double value() const { return m_value; } /** * @brief Returns minimum of the allowed value range. * \sa setMinimum, setRange */ double minimum() const { return m_min; } /** * @brief Returns maximum of the allowed value range. * \sa setMaximum, setRange */ double maximum() const { return m_max; } public Q_SLOTS: /** * @brief Defines minimum und maximum of the allowed value range. * If the current value does not fit into the range, it will be automatically adjusted. * @param min minimum of the allowed value range * @param max maximum of the allowed value range */ void setRange(double min, double max); /** * @brief Defines minimum of the allowed value range. * If the current value does not fit into the range, it will be automatically adjusted. * @param min minimum of the allowed value range * \sa setRange */ void setMinimum(double min); /** * @brief Defines maximum of the allowed value range. * If the current value does not fit into the range, it will be automatically adjusted. * @param max maximum of the allowed value range * \sa setRange */ void setMaximum(double max); /** * @brief Sets a value which will be shown on the widget. * @param val must be between minimum() and maximum() */ void setValue(double val); /** * @brief Integer version of the previous slot. * @param val must be between minimum() and maximum() */ void setValue(int val); protected: void paintEvent(QPaintEvent *event) override; virtual void drawBackground(QPainter &p, const QRectF &baseRect); virtual void drawBase(QPainter &p, const QRectF &baseRect); virtual void drawValue(QPainter &p, const QRectF &baseRect, double value, double arcLength); virtual void calculateInnerRect(const QRectF &baseRect, double outerRadius, QRectF &innerRect, double &innerRadius); virtual void drawInnerBackground(QPainter &p, const QRectF &innerRect); virtual void drawText(QPainter &p, const QRectF &innerRect, double innerRadius, double value); virtual QString valueToText(double value) const; virtual void valueFormatChanged(); QSize minimumSizeHint() const override { return QSize(32, 32); } bool hasHeightForWidth() const override { return true; } int heightForWidth(int w) const override { return w; } void rebuildDataBrushIfNeeded(); double m_min, m_max; double m_value; double m_nullPosition; BarStyle m_barStyle; double m_outlinePenWidth, m_dataPenWidth; QGradientStops m_gradientData; bool m_rebuildBrush; QString m_format; int m_decimals; static const int UF_VALUE = 1; static const int UF_PERCENT = 2; static const int UF_MAX = 4; int m_updateFlags; }; #endif // QROUNDPROGRESSBAR_H diff --git a/kstars/auxiliary/imageexporter.cpp b/kstars/auxiliary/imageexporter.cpp index d34742e8e..40babb273 100644 --- a/kstars/auxiliary/imageexporter.cpp +++ b/kstars/auxiliary/imageexporter.cpp @@ -1,288 +1,288 @@ /*************************************************************************** imageexporter.cpp - K Desktop Planetarium ------------------- begin : Sun 13 Jan 2013 00:53:50 CST copyright : (c) 2013 by Akarsh Simha email : akarsh.simha@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ /* Project Includes */ #include "imageexporter.h" #include "kstars.h" #include "skyqpainter.h" #include "skymap.h" #include #include /* Qt Includes */ #include #include #include -ImageExporter::ImageExporter(QObject *parent) : QObject(parent), m_includeLegend(false), m_Size(0) +ImageExporter::ImageExporter(QObject *parent) : QObject(parent), m_includeLegend(false), m_Size(nullptr) { m_Legend = new Legend; // set font for legend labels m_Legend->setFont(QFont("Courier New", 8)); // set up the default alpha setLegendAlpha(160); } void ImageExporter::exportSvg(const QString &fileName) { SkyMap *map = SkyMap::Instance(); // export as SVG QSvgGenerator svgGenerator; svgGenerator.setFileName(fileName); svgGenerator.setTitle(i18n("KStars Exported Sky Image")); svgGenerator.setDescription(i18n("KStars Exported Sky Image")); svgGenerator.setSize(QSize(map->width(), map->height())); svgGenerator.setResolution(qMax(map->logicalDpiX(), map->logicalDpiY())); svgGenerator.setViewBox(QRect(0, 0, map->width(), map->height())); SkyQPainter painter(KStars::Instance(), &svgGenerator); painter.begin(); map->exportSkyImage(&painter); if (m_includeLegend) { addLegend(&painter); } painter.end(); } bool ImageExporter::exportRasterGraphics(const QString &fileName) { //Determine desired image format from filename extension QString ext = fileName.mid(fileName.lastIndexOf(".") + 1); // export as raster graphics const char *format = "PNG"; if (ext.toLower() == "png") { format = "PNG"; } else if (ext.toLower() == "jpg" || ext.toLower() == "jpeg") { format = "JPG"; } else if (ext.toLower() == "gif") { format = "GIF"; } else if (ext.toLower() == "pnm") { format = "PNM"; } else if (ext.toLower() == "bmp") { format = "BMP"; } else { qWarning() << i18n("Could not parse image format of %1; assuming PNG.", fileName); } SkyMap *map = SkyMap::Instance(); int width, height; if (m_Size) { width = m_Size->width(); height = m_Size->height(); } else { width = map->width(); height = map->height(); } QPixmap skyimage(map->width(), map->height()); QPixmap outimage(width, height); outimage.fill(); map->exportSkyImage(&skyimage); qApp->processEvents(); //skyImage is the size of the sky map. The requested image size is w x h. //If w x h is smaller than the skymap, then we simply crop the image. //If w x h is larger than the skymap, pad the skymap image with a white border. if (width == map->width() && height == map->height()) { outimage = skyimage.copy(); } else { int dx(0), dy(0), sx(0), sy(0); int sw(map->width()), sh(map->height()); if (width > map->width()) { dx = (width - map->width()) / 2; } else { sx = (map->width() - width) / 2; sw = width; } if (height > map->height()) { dy = (height - map->height()) / 2; } else { sy = (map->height() - height) / 2; sh = height; } QPainter p; p.begin(&outimage); p.fillRect(outimage.rect(), QBrush(Qt::white)); p.drawImage(dx, dy, skyimage.toImage(), sx, sy, sw, sh); p.end(); } if (m_includeLegend) { addLegend(&outimage); } if (!outimage.save(fileName, format)) { m_lastErrorMessage = i18n("Error: Unable to save image: %1 ", fileName); qDebug() << m_lastErrorMessage; return false; } else { KStars::Instance()->statusBar()->showMessage(i18n("Saved image to %1", fileName)); return true; } } void ImageExporter::addLegend(SkyQPainter *painter) { m_Legend->paintLegend(painter); } void ImageExporter::addLegend(QPaintDevice *pd) { SkyQPainter painter(KStars::Instance(), pd); painter.begin(); addLegend(&painter); painter.end(); } bool ImageExporter::exportImage(QString url) { QUrl fileURL = QUrl::fromUserInput(url); m_lastErrorMessage = QString(); if (fileURL.isValid()) { QTemporaryFile tmpfile; QString fname; bool isLocalFile = fileURL.isLocalFile(); if (isLocalFile) { fname = fileURL.toLocalFile(); } else { tmpfile.open(); fname = tmpfile.fileName(); } //Determine desired image format from filename extension QString ext = fname.mid(fname.lastIndexOf(".") + 1); if (ext.toLower() == "svg") { exportSvg(fname); } else { return exportRasterGraphics(fname); } if (!isLocalFile) { //attempt to upload image to remote location KIO::StoredTransferJob *put_job = KIO::storedHttpPost(&tmpfile, fileURL, -1); //if(!KIO::NetAccess::upload(tmpfile.fileName(), fileURL, m_KStars)) if (put_job->exec() == false) { m_lastErrorMessage = i18n("Could not upload image to remote location: %1", fileURL.url()); qWarning() << m_lastErrorMessage; return false; } } return true; } m_lastErrorMessage = i18n("Could not export image: URL %1 invalid", fileURL.url()); qWarning() << m_lastErrorMessage; return false; } void ImageExporter::setLegendProperties(Legend::LEGEND_TYPE type, Legend::LEGEND_ORIENTATION orientation, Legend::LEGEND_POSITION position, int alpha, bool include) { // set background color (alpha) setLegendAlpha(alpha); // set legend orientation m_Legend->setOrientation(orientation); // set legend type m_Legend->setType(type); // set legend position m_Legend->setPosition(position); m_includeLegend = include; } ImageExporter::~ImageExporter() { delete m_Legend; } void ImageExporter::setRasterOutputSize(const QSize *size) { if (size) m_Size = new QSize(*size); // make a copy, so it's safe if the original gets deleted else m_Size = nullptr; } void ImageExporter::setLegendAlpha(int alpha) { Q_ASSERT(alpha >= 0 && alpha <= 255); Q_ASSERT(m_Legend); QColor bgColor = m_Legend->getBgColor(); bgColor.setAlpha(alpha); m_Legend->setBgColor(bgColor); } diff --git a/kstars/auxiliary/imageviewer.cpp b/kstars/auxiliary/imageviewer.cpp index 3911c320b..aa46a6256 100644 --- a/kstars/auxiliary/imageviewer.cpp +++ b/kstars/auxiliary/imageviewer.cpp @@ -1,377 +1,377 @@ /*************************************************************************** imageviewer.cpp - An ImageViewer for KStars ------------------- begin : Mon Aug 27 2001 copyright : (C) 2001 by Thomas Kabelmann email : tk78@gmx.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. * * * ***************************************************************************/ #include "imageviewer.h" #ifndef KSTARS_LITE #include "kstars.h" #endif #ifndef KSTARS_LITE #include #endif #include #include #include #include #include #include QUrl ImageViewer::lastURL = QUrl::fromLocalFile(QDir::homePath()); ImageLabel::ImageLabel(QWidget *parent) : QFrame(parent) { #ifndef KSTARS_LITE setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); setFrameStyle(QFrame::StyledPanel | QFrame::Plain); setLineWidth(2); #endif } ImageLabel::~ImageLabel() { } void ImageLabel::setImage(const QImage &img) { #ifndef KSTARS_LITE m_Image = img; pix = QPixmap::fromImage(m_Image); #endif } void ImageLabel::invertPixels() { #ifndef KSTARS_LITE m_Image.invertPixels(); pix = QPixmap::fromImage(m_Image.scaled(width(), height(), Qt::KeepAspectRatio)); #endif } void ImageLabel::paintEvent(QPaintEvent *) { #ifndef KSTARS_LITE QPainter p; p.begin(this); int x = 0; if (pix.width() < width()) x = (width() - pix.width()) / 2; p.drawPixmap(x, 0, pix); p.end(); #endif } void ImageLabel::resizeEvent(QResizeEvent *event) { int w = pix.width(); int h = pix.height(); if (event->size().width() == w && event->size().height() == h) return; pix = QPixmap::fromImage(m_Image.scaled(event->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } -ImageViewer::ImageViewer(const QString &caption, QWidget *parent) : QDialog(parent), fileIsImage(false), downloadJob(0) +ImageViewer::ImageViewer(const QString &caption, QWidget *parent) : QDialog(parent), fileIsImage(false), downloadJob(nullptr) { #ifndef KSTARS_LITE init(caption, QString()); #endif } ImageViewer::ImageViewer(const QUrl &url, const QString &capText, QWidget *parent) : QDialog(parent), m_ImageUrl(url) { #ifndef KSTARS_LITE init(url.fileName(), capText); // check URL if (!m_ImageUrl.isValid()) qDebug() << "URL is malformed: " << m_ImageUrl; if (m_ImageUrl.isLocalFile()) { loadImage(m_ImageUrl.toLocalFile()); return; } { QTemporaryFile tempfile; tempfile.open(); file.setFileName(tempfile.fileName()); } // we just need the name and delete the tempfile from disc; if we don't do it, a dialog will be show loadImageFromURL(); #endif } void ImageViewer::init(QString caption, QString capText) { #ifndef KSTARS_LITE setAttribute(Qt::WA_DeleteOnClose, true); setModal(false); setWindowTitle(i18n("KStars image viewer: %1", caption)); // Create widget QFrame *page = new QFrame(this); //setMainWidget( page ); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(page); setLayout(mainLayout); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); QPushButton *invertB = new QPushButton(i18n("Invert colors")); invertB->setToolTip(i18n("Reverse colors of the image. This is useful to enhance contrast at times. This affects " "only the display and not the saving.")); QPushButton *saveB = new QPushButton( QIcon::fromTheme("document-save", QIcon(":/icons/breeze/default/document-save.svg")), i18n("Save")); saveB->setToolTip(i18n("Save the image to disk")); buttonBox->addButton(invertB, QDialogButtonBox::ActionRole); buttonBox->addButton(saveB, QDialogButtonBox::ActionRole); connect(invertB, SIGNAL(clicked()), this, SLOT(invertColors())); connect(saveB, SIGNAL(clicked()), this, SLOT(saveFileToDisc())); m_View = new ImageLabel(page); m_View->setAutoFillBackground(true); m_Caption = new QLabel(page); m_Caption->setAutoFillBackground(true); m_Caption->setFrameShape(QFrame::StyledPanel); m_Caption->setText(capText); // Add layout QVBoxLayout *vlay = new QVBoxLayout(page); vlay->setSpacing(0); vlay->setMargin(0); vlay->addWidget(m_View); vlay->addWidget(m_Caption); //Reverse colors QPalette p = palette(); p.setColor(QPalette::Window, palette().color(QPalette::WindowText)); p.setColor(QPalette::WindowText, palette().color(QPalette::Window)); m_Caption->setPalette(p); m_View->setPalette(p); //If the caption is wider than the image, try to shrink the font a bit QFont capFont = m_Caption->font(); capFont.setPointSize(capFont.pointSize() - 2); m_Caption->setFont(capFont); #endif } ImageViewer::~ImageViewer() { QString filename = file.fileName(); if (filename.startsWith(QLatin1String("/tmp/")) || filename.contains("/Temp")) { if (m_ImageUrl.isEmpty() == false || - KMessageBox::questionYesNo(0, i18n("Remove temporary file %1 from disk?", filename), + KMessageBox::questionYesNo(nullptr, i18n("Remove temporary file %1 from disk?", filename), i18n("Confirm Removal"), KStandardGuiItem::yes(), KStandardGuiItem::no(), i18n("imageviewer_temporary_file_removal")) == KMessageBox::Yes) QFile::remove(filename); } QApplication::restoreOverrideCursor(); } void ImageViewer::loadImageFromURL() { #ifndef KSTARS_LITE QUrl saveURL = QUrl::fromLocalFile(file.fileName()); if (!saveURL.isValid()) qWarning() << "tempfile-URL is malformed"; QApplication::setOverrideCursor(Qt::WaitCursor); downloadJob.setProgressDialogEnabled(true, i18n("Download"), i18n("Please wait while image is being downloaded...")); connect(&downloadJob, SIGNAL(downloaded()), this, SLOT(downloadReady())); connect(&downloadJob, SIGNAL(canceled()), this, SLOT(close())); connect(&downloadJob, SIGNAL(error(QString)), this, SLOT(downloadError(QString))); downloadJob.get(m_ImageUrl); #endif } void ImageViewer::downloadReady() { #ifndef KSTARS_LITE QApplication::restoreOverrideCursor(); if (file.open(QFile::WriteOnly)) { file.write(downloadJob.downloadedData()); file.close(); // to get the newest information from the file and not any information from opening of the file if (file.exists()) { showImage(); return; } close(); } else - KMessageBox::error(0, file.errorString(), i18n("Image Viewer")); + KMessageBox::error(nullptr, file.errorString(), i18n("Image Viewer")); #endif } void ImageViewer::downloadError(const QString &errorString) { #ifndef KSTARS_LITE QApplication::restoreOverrideCursor(); KMessageBox::error(this, errorString); #endif } bool ImageViewer::loadImage(const QString &filename) { #ifndef KSTARS_LITE file.setFileName(filename); return showImage(); #else return false; #endif } bool ImageViewer::showImage() { #ifndef KSTARS_LITE QImage image; if (!image.load(file.fileName())) { QString text = i18n("Loading of the image %1 failed.", m_ImageUrl.url()); KMessageBox::error(this, text); close(); return false; } fileIsImage = true; // we loaded the file and know now, that it is an image //If the image is larger than screen width and/or screen height, //shrink it to fit the screen QRect deskRect = QApplication::desktop()->availableGeometry(); int w = deskRect.width(); // screen width int h = deskRect.height(); // screen height if (image.width() <= w && image.height() > h) //Window is taller than desktop image = image.scaled(int(image.width() * h / image.height()), h); else if (image.height() <= h && image.width() > w) //window is wider than desktop image = image.scaled(w, int(image.height() * w / image.width())); else if (image.width() > w && image.height() > h) //window is too tall and too wide { //which needs to be shrunk least, width or height? float fx = float(w) / float(image.width()); float fy = float(h) / float(image.height()); if (fx > fy) //width needs to be shrunk less, so shrink to fit in height image = image.scaled(int(image.width() * fy), h); else //vice versa image = image.scaled(w, int(image.height() * fx)); } show(); // hide is default m_View->setImage(image); w = image.width(); //If the caption is wider than the image, set the window size //to fit the caption if (m_Caption->width() > w) w = m_Caption->width(); //setFixedSize( w, image.height() + m_Caption->height() ); resize(w, image.height()); update(); show(); return true; #else return false; #endif } void ImageViewer::saveFileToDisc() { #ifndef KSTARS_LITE QFileDialog dialog; QUrl newURL = dialog.getSaveFileUrl(KStars::Instance(), i18n("Save Image"), lastURL); // save-dialog with default filename if (!newURL.isEmpty()) { //QFile f (newURL.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).toLocalFile() + '/' + newURL.fileName()); QFile f(newURL.toLocalFile()); if (f.exists()) { int r = KMessageBox::warningContinueCancel(static_cast(parent()), i18n("A file named \"%1\" already exists. " "Overwrite it?", newURL.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite()); if (r == KMessageBox::Cancel) return; f.remove(); } lastURL = QUrl(newURL.toString(QUrl::RemoveFilename)); saveFile(newURL); } #endif } void ImageViewer::saveFile(QUrl &url) { // synchronous access to prevent segfaults //if (!KIO::NetAccess::file_copy (QUrl (file.fileName()), url, (QWidget*) 0)) //QUrl tmpURL((file.fileName())); //tmpURL.setScheme("file"); if (file.copy(url.toLocalFile()) == false) //if (KIO::file_copy(tmpURL, url)->exec() == false) { QString text = i18n("Saving of the image %1 failed.", url.toString()); #ifndef KSTARS_LITE KMessageBox::error(this, text); #else qDebug() << text; #endif } #ifndef KSTARS_LITE else KStars::Instance()->statusBar()->showMessage(i18n("Saved image to %1", url.toString())); #endif } void ImageViewer::invertColors() { #ifndef KSTARS_LITE // Invert colors m_View->invertPixels(); m_View->update(); #endif } diff --git a/kstars/auxiliary/ksdssdownloader.cpp b/kstars/auxiliary/ksdssdownloader.cpp index f5df36e48..7281d938f 100644 --- a/kstars/auxiliary/ksdssdownloader.cpp +++ b/kstars/auxiliary/ksdssdownloader.cpp @@ -1,352 +1,352 @@ /*************************************************************************** ksdssdownloader.cpp - K Desktop Planetarium ------------------- begin : Tue 05 Jan 2016 03:39:18 CST copyright : (c) 2016 by Akarsh Simha email : akarsh.simha@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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 "ksdssdownloader.h" #include "deepskyobject.h" #include "Options.h" #include "auxiliary/filedownloader.h" #include #include KSDssDownloader::KSDssDownloader(QObject *parent) : QObject(parent) { connect(this, &KSDssDownloader::downloadCanceled, this, [&]() { deleteLater(); }); m_VersionPreference << "poss2ukstu_blue" << "poss2ukstu_red" << "poss1_blue" << "poss1_red" << "quickv" << "poss2ukstu_ir"; m_TempFile.open(); } KSDssDownloader::KSDssDownloader(const SkyPoint *const p, const QString &destFileName, const std::function &slotDownloadReady, QObject *parent) : QObject(parent) { // Initialize version preferences. FIXME: This must be made a // user-changeable option just in case someone likes red connect(this, &KSDssDownloader::downloadCanceled, this, [&]() { deleteLater(); }); m_VersionPreference << "poss2ukstu_blue" << "poss2ukstu_red" << "poss1_blue" << "poss1_red" << "quickv" << "poss2ukstu_ir"; m_TempFile.open(); connect(this, &KSDssDownloader::downloadComplete, slotDownloadReady); startDownload(p, destFileName); } QString KSDssDownloader::getDSSURL(const SkyPoint *const p, const QString &version, struct KSDssImage::Metadata *md) { - const DeepSkyObject *dso = 0; + const DeepSkyObject *dso = nullptr; double height, width; double dss_default_size = Options::defaultDSSImageSize(); double dss_padding = Options::dSSPadding(); Q_ASSERT(p); Q_ASSERT(dss_default_size > 0.0 && dss_padding >= 0.0); dso = dynamic_cast(p); // Decide what to do about the height and width if (dso) { // For deep-sky objects, use their height and width information double a, b, pa; a = dso->a(); b = dso->a() * dso->e(); // Use a * e instead of b, since e() returns 1 whenever one of the dimensions is zero. This is important for circular objects Q_ASSERT(a >= b); pa = dso->pa() * dms::DegToRad; // We now want to convert a, b, and pa into an image // height and width -- i.e. a dRA and dDec. // DSS uses dDec for height and dRA for width. (i.e. "top" is north in the DSS images, AFAICT) // From some trigonometry, assuming we have a rectangular object (worst case), we need: width = a * sin(pa) + b * fabs(cos(pa)); height = a * fabs(cos(pa)) + b * sin(pa); // 'a' and 'b' are in arcminutes, so height and width are in arcminutes // Pad the RA and Dec, so that we show more of the sky than just the object. height += dss_padding; width += dss_padding; } else { // For a generic sky object, we don't know what to do. So // we just assume the default size. height = width = dss_default_size; } // There's no point in tiny DSS images that are smaller than dss_default_size if (height < dss_default_size) height = dss_default_size; if (width < dss_default_size) width = dss_default_size; return getDSSURL(p, width, height, version, md); } QString KSDssDownloader::getDSSURL(const SkyPoint *const p, float width, float height, const QString &version, struct KSDssImage::Metadata *md) { Q_ASSERT(p); if (!p) return QString(); if (width <= 0) return QString(); if (height <= 0) height = width; QString URL = getDSSURL(p->ra0(), p->dec0(), width, height, "gif", version, md); if (md) { - const SkyObject *obj = 0; + const SkyObject *obj = nullptr; obj = dynamic_cast(p); if (obj && obj->hasName()) md->object = obj->name(); } return URL; } QString KSDssDownloader::getDSSURL(const dms &ra, const dms &dec, float width, float height, const QString &type_, const QString &version_, struct KSDssImage::Metadata *md) { const double dss_default_size = Options::defaultDSSImageSize(); QString version = version_.toLower(); QString type = type_.toLower(); QString URLprefix = QString("http://archive.stsci.edu/cgi-bin/dss_search?v=%1&").arg(version); QString URLsuffix = QString("&e=J2000&f=%1&c=none&fov=NONE").arg(type); char decsgn = (dec.Degrees() < 0.0) ? '-' : '+'; int dd = abs(dec.degree()); int dm = abs(dec.arcmin()); int ds = abs(dec.arcsec()); // Infinite and NaN sizes are replaced by the default size and tiny DSS images are resized to default size if (!qIsFinite(height) || height <= 0.0) height = dss_default_size; if (!qIsFinite(width) || width <= 0.0) width = dss_default_size; // DSS accepts images that are no larger than 75 arcminutes if (height > 75.0) height = 75.0; if (width > 75.0) width = 75.0; QString RAString, DecString, SizeString; DecString = DecString.sprintf("&d=%c%02d+%02d+%02d", decsgn, dd, dm, ds); RAString = RAString.sprintf("r=%02d+%02d+%02d", ra.hour(), ra.minute(), ra.second()); SizeString = SizeString.sprintf("&h=%02.1f&w=%02.1f", height, width); if (md) { md->src = KSDssImage::Metadata::DSS; md->width = width; md->height = height; md->ra0 = ra; md->dec0 = dec; md->version = version; md->format = (type.contains("fit") ? KSDssImage::Metadata::FITS : KSDssImage::Metadata::GIF); md->height = height; md->width = width; if (version.contains("poss2")) md->gen = 2; else if (version.contains("poss1")) md->gen = 1; else if (version.contains("quickv")) md->gen = 4; else md->gen = -1; if (version.contains("red")) md->band = 'R'; else if (version.contains("blue")) md->band = 'B'; else if (version.contains("ir")) md->band = 'I'; else if (version.contains("quickv")) md->band = 'V'; else md->band = '?'; md->object = QString(); } return (URLprefix + RAString + DecString + SizeString + URLsuffix); } void KSDssDownloader::initiateSingleDownloadAttempt(QUrl srcUrl) { qDebug() << "Temp file is at " << m_TempFile.fileName(); QUrl fileUrl = QUrl::fromLocalFile(m_TempFile.fileName()); qDebug() << "Attempt #" << m_attempt << "downloading DSS Image. URL: " << srcUrl << " to " << fileUrl; //m_DownloadJob = KIO::copy( srcUrl, fileUrl, KIO::Overwrite ) ; // FIXME: Can be done with pure Qt //connect ( m_DownloadJob, SIGNAL ( result (KJob *) ), SLOT ( downloadAttemptFinished() ) ); downloadJob = new FileDownloader(); downloadJob->setProgressDialogEnabled(true, i18n("DSS Download"), i18n("Please wait while DSS image is being downloaded...")); connect(downloadJob, SIGNAL(canceled()), this, SIGNAL(downloadCanceled())); connect(downloadJob, SIGNAL(downloaded()), this, SLOT(downloadAttemptFinished())); connect(downloadJob, SIGNAL(error(QString)), this, SLOT(downloadError(QString))); downloadJob->get(srcUrl); } void KSDssDownloader::startDownload(const SkyPoint *const p, const QString &destFileName) { QUrl srcUrl; m_FileName = destFileName; m_attempt = 0; srcUrl.setUrl(getDSSURL(p, m_VersionPreference[m_attempt], &m_AttemptData)); initiateSingleDownloadAttempt(srcUrl); } void KSDssDownloader::startSingleDownload(const QUrl srcUrl, const QString &destFileName, KSDssImage::Metadata &md) { m_FileName = destFileName; QUrl fileUrl = QUrl::fromLocalFile(m_TempFile.fileName()); qDebug() << "Downloading DSS Image from URL: " << srcUrl << " to " << fileUrl; //m_DownloadJob = KIO::copy( srcUrl, fileUrl, KIO::Overwrite ) ; // FIXME: Can be done with pure Qt //connect ( m_DownloadJob, SIGNAL ( result (KJob *) ), SLOT ( singleDownloadFinished() ) ); downloadJob = new FileDownloader(); downloadJob->setProgressDialogEnabled(true, i18n("DSS Download"), i18n("Please wait while DSS image is being downloaded...")); connect(downloadJob, SIGNAL(canceled()), this, SIGNAL(downloadCanceled())); connect(downloadJob, SIGNAL(downloaded()), this, SLOT(singleDownloadFinished())); connect(downloadJob, SIGNAL(error(QString)), this, SLOT(downloadError(QString))); m_AttemptData = md; downloadJob->get(srcUrl); } void KSDssDownloader::downloadError(const QString &errorString) { qDebug() << "Error " << errorString << " downloading DSS images!"; emit downloadComplete(false); downloadJob->deleteLater(); return; } void KSDssDownloader::singleDownloadFinished() { m_TempFile.open(); m_TempFile.write(downloadJob->downloadedData()); m_TempFile.close(); downloadJob->deleteLater(); // Check if we have a proper DSS image or the DSS server failed QMimeDatabase mdb; QMimeType mt = mdb.mimeTypeForFile(m_TempFile.fileName(), QMimeDatabase::MatchContent); if (mt.name().contains("image", Qt::CaseInsensitive)) { qDebug() << "DSS download was successful"; emit downloadComplete(writeImageWithMetadata(m_TempFile.fileName(), m_FileName, m_AttemptData)); return; } else emit downloadComplete(false); } void KSDssDownloader::downloadAttemptFinished() { if (m_AttemptData.src == KSDssImage::Metadata::SDSS) { // FIXME: do SDSS-y things emit downloadComplete(false); deleteLater(); downloadJob->deleteLater(); return; } else { m_TempFile.open(); m_TempFile.write(downloadJob->downloadedData()); m_TempFile.close(); downloadJob->deleteLater(); // Check if we have a proper DSS image or the DSS server failed QMimeDatabase mdb; QMimeType mt = mdb.mimeTypeForFile(m_TempFile.fileName(), QMimeDatabase::MatchContent); if (mt.name().contains("image", Qt::CaseInsensitive)) { qDebug() << "DSS download was successful"; emit downloadComplete(writeImageFile()); deleteLater(); return; } // We must have failed, try the next attempt QUrl srcUrl; m_attempt++; if (m_attempt == m_VersionPreference.count()) { // Nothing downloaded... very strange. Fail. qDebug() << "Error downloading DSS images: All alternatives failed!"; emit downloadComplete(false); deleteLater(); return; } srcUrl.setUrl(getDSSURL(m_AttemptData.ra0, m_AttemptData.dec0, m_AttemptData.width, m_AttemptData.height, ((m_AttemptData.format == KSDssImage::Metadata::FITS) ? "fits" : "gif"), m_VersionPreference[m_attempt], &m_AttemptData)); initiateSingleDownloadAttempt(srcUrl); } } bool KSDssDownloader::writeImageFile() { return writeImageWithMetadata(m_TempFile.fileName(), m_FileName, m_AttemptData); } bool KSDssDownloader::writeImageWithMetadata(const QString &srcFile, const QString &destFile, const KSDssImage::Metadata &md) { // Write the temporary file into an image file with metadata QImage img(srcFile); QImageWriter writer(destFile, "png"); writer.setText("Calibrated", "true"); // This means that the image has RA/Dec size and orientation that is calibrated writer.setText("PA", "0"); // Position Angle is zero degrees for DSS images writer.setText("Source", QString::number(KSDssImage::Metadata::DSS)); writer.setText("Format", QString::number(KSDssImage::Metadata::PNG)); writer.setText("Version", md.version); writer.setText("Object", md.object); writer.setText("RA", md.ra0.toHMSString(true)); writer.setText("Dec", md.dec0.toDMSString(true, true)); writer.setText("Width", QString::number(md.width)); writer.setText("Height", QString::number(md.height)); writer.setText("Band", QString() + md.band); writer.setText("Generation", QString::number(md.gen)); writer.setText("Author", "KStars KSDssDownloader"); return writer.write(img); } diff --git a/kstars/auxiliary/ksdssdownloader.h b/kstars/auxiliary/ksdssdownloader.h index 80e427e7b..7df96abb9 100644 --- a/kstars/auxiliary/ksdssdownloader.h +++ b/kstars/auxiliary/ksdssdownloader.h @@ -1,134 +1,134 @@ /*************************************************************************** ksdssdownloader.h - K Desktop Planetarium ------------------- begin : Tue 05 Jan 2016 03:22:50 CST copyright : (c) 2016 by Akarsh Simha email : akarsh.simha@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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 "ksdssimage.h" #include #include #include #include #include #include class FileDownloader; class SkyPoint; class dms; /** * @class KSDssDownloader * @short Helps download a DSS image * @author Akarsh Simha * * @note This object is designed to commit suicide (calls * QObject::deleteLater() )! Never allocate this using anything but * new -- do not allocate it on the stack! This is ideal for its * operation, as it deletes itself after downloading. */ class KSDssDownloader : public QObject { Q_OBJECT public: /** @short Constructor */ - explicit KSDssDownloader(QObject *parent = 0); + explicit KSDssDownloader(QObject *parent = nullptr); /** * @short Constructor that initiates a "standard" DSS download job, calls the downloadReady slot, and finally self destructs * @note Very important that if you create with this constructor, * the object will self-destruct. Avoid keeping pointers to it, or * things may segfault! */ KSDssDownloader(const SkyPoint *const p, const QString &destFileName, - const std::function &slotDownloadReady, QObject *parent = 0); + const std::function &slotDownloadReady, QObject *parent = nullptr); /** * @short Stateful single-download of a supplied URL. Use when the flexibility is required * @note Does not self-delete this object. Construct with default constructor, and delete as usual. * @param srcUrl source DSS URL to download * @param destFileName destination image file (will be of PNG format) * @param md DSS image metadata to write into image file * @note emits downloadComplete with success state when done */ void startSingleDownload(const QUrl srcUrl, const QString &destFileName, KSDssImage::Metadata &md); /** * @short High-level method to create a URL to obtain a DSS image for a given SkyPoint * @note If SkyPoint is a DeepSkyObject, this method automatically * decides the image size required to fit the object. * @note Moved from namespace KSUtils (--asimha, Jan 5 2016) */ static QString getDSSURL(const SkyPoint *const p, const QString &version = "all", - struct KSDssImage::Metadata *md = 0); + struct KSDssImage::Metadata *md = nullptr); /** * @short High-level method to create a URL to obtain a DSS image for a given SkyPoint * @note This method includes an option to set the height, but uses default values for many parameters */ static QString getDSSURL(const SkyPoint *const p, float width, float height = 0, const QString &version = "all", - struct KSDssImage::Metadata *md = 0); + struct KSDssImage::Metadata *md = nullptr); /** * @short Create a URL to obtain a DSS image for a given RA, Dec * @param RA The J2000.0 Right Ascension of the point * @param Dec The J2000.0 Declination of the point * @param width The width of the image in arcminutes * @param height The height of the image in arcminutes * @param version string describing which version to get * @param type The image type, either gif or fits. * @param md If a valid pointer is provided, fill with metadata * @note This method resets height and width to fall within the range accepted by DSS * @note Moved from namespace KSUtils (--asimha, Jan 5 2016) * * @note Valid versions are: dss1, poss2ukstu_red, poss2ukstu_ir, * poss2ukstu_blue, poss1_blue, poss1_red, all, quickv, * phase2_gsc2, phase2_gsc1. Of these, dss1 uses POSS1 Red in the * north and POSS2/UKSTU Blue in the south. all uses the best of a * combined list of all plates. * */ static QString getDSSURL(const dms &ra, const dms &dec, float width = 0, float height = 0, const QString &type_ = "gif", const QString &version_ = "all", - struct KSDssImage::Metadata *md = 0); + struct KSDssImage::Metadata *md = nullptr); /** @short Write image metadata into file */ static bool writeImageWithMetadata(const QString &srcFile, const QString &destFile, const KSDssImage::Metadata &md); signals: void downloadComplete(bool success); void downloadCanceled(); private slots: void downloadAttemptFinished(); void singleDownloadFinished(); void downloadError(const QString &errorString); private: void startDownload(const SkyPoint *const p, const QString &destFileName); void initiateSingleDownloadAttempt(QUrl srcUrl); bool writeImageFile(); QStringList m_VersionPreference; int m_attempt { 0 }; struct KSDssImage::Metadata m_AttemptData; QString m_FileName; QTemporaryFile m_TempFile; FileDownloader *downloadJob { nullptr }; }; diff --git a/kstars/auxiliary/ksnotification.cpp b/kstars/auxiliary/ksnotification.cpp index 7f8cbfc85..b1a2dbdb2 100644 --- a/kstars/auxiliary/ksnotification.cpp +++ b/kstars/auxiliary/ksnotification.cpp @@ -1,68 +1,68 @@ /* General KStars Notifications for desktop and lite version Copyright (C) 2016 Jasem Mutlaq (mutlaqja@ikarustech.com) 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 "ksnotification.h" #ifdef KSTARS_LITE #include "kstarslite.h" #else #include #include #endif namespace KSNotification { void error(const QString &message, const QString &title) { #ifdef KSTARS_LITE Q_UNUSED(title); KStarsLite::Instance()->notificationMessage(message); #else - KMessageBox::error(0, message, title); + KMessageBox::error(nullptr, message, title); #endif } void sorry(const QString &message, const QString &title) { #ifdef KSTARS_LITE Q_UNUSED(title); KStarsLite::Instance()->notificationMessage(message); #else - KMessageBox::sorry(0, message, title); + KMessageBox::sorry(nullptr, message, title); #endif } void info(const QString &message, const QString &title) { #ifdef KSTARS_LITE Q_UNUSED(title); KStarsLite::Instance()->notificationMessage(message); #else - KMessageBox::information(0, message, title); + KMessageBox::information(nullptr, message, title); #endif } void transient(const QString &message, const QString &title) { #ifdef KSTARS_LITE Q_UNUSED(title); KStarsLite::Instance()->notificationMessage(message); #else QPointer msgBox = new QMessageBox(); msgBox->setAttribute(Qt::WA_DeleteOnClose); msgBox->setWindowTitle(title); msgBox->setText(message); msgBox->setModal(false); msgBox->setIcon(QMessageBox::Warning); msgBox->show(); #endif } } diff --git a/kstars/auxiliary/ksuserdb.cpp b/kstars/auxiliary/ksuserdb.cpp index aebc2c6de..dc4b15ff3 100644 --- a/kstars/auxiliary/ksuserdb.cpp +++ b/kstars/auxiliary/ksuserdb.cpp @@ -1,1825 +1,1825 @@ /*************************************************************************** ksuserdb.cpp - K Desktop Planetarium ------------------- begin : Wed May 2 2012 copyright : (C) 2012 by Rishab Arora email : ra.rishab@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. * * * ***************************************************************************/ #include "ksuserdb.h" #include "artificialhorizoncomponent.h" #include "kspaths.h" #include "kstarsdata.h" #include "linelist.h" #include "version.h" #include #include #include #include /* * TODO (spacetime): * The database supports storing logs. But it needs to be implemented. * * One of the unresolved problems was the creation of a unique identifier * for each object (DSO,planet,star etc) for use in the database. */ KSUserDB::~KSUserDB() { userdb_.close(); } bool KSUserDB::Initialize() { // Every logged in user has their own db. userdb_ = QSqlDatabase::addDatabase("QSQLITE", "userdb"); QString dbfile = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "userdb.sqlite"; QFile testdb(dbfile); bool first_run = false; if (!testdb.exists()) { qCInfo(KSTARS) << "User DB does not exist. New User DB will be created."; first_run = true; } userdb_.setDatabaseName(dbfile); if (!userdb_.open()) { qCWarning(KSTARS) << "Unable to open user database file."; qCritical(KSTARS) << LastError(); return false; } else { qCDebug(KSTARS) << "Opened the User DB. Ready."; if (first_run == true) FirstRun(); else { // Update table if previous version exists - QSqlTableModel version(0, userdb_); + QSqlTableModel version(nullptr, userdb_); version.setTable("Version"); version.select(); QSqlRecord record = version.record(0); version.clear(); QString currentDBVersion = record.value("Version").toString(); // Update database version to current KStars version if (currentDBVersion != KSTARS_VERSION) { QSqlQuery query(userdb_); QString versionQuery = QString("UPDATE Version SET Version='%1'").arg(KSTARS_VERSION); if (!query.exec(versionQuery)) qCWarning(KSTARS) << query.lastError(); } // If prior to 2.4.0 upgrade database for horizon table if (currentDBVersion < "2.4.0") { QSqlQuery query(userdb_); if (!query.exec("CREATE TABLE IF NOT EXISTS horizons (id INTEGER DEFAULT NULL PRIMARY KEY " "AUTOINCREMENT, name TEXT NOT NULL, label TEXT NOT NULL, enabled INTEGER NOT NULL)")) qCWarning(KSTARS) << query.lastError(); } // If prior to 2.6.0 upgrade database for profiles tables if (currentDBVersion < "2.6.0") { QSqlQuery query(userdb_); QString versionQuery = QString("UPDATE Version SET Version='%1'").arg(KSTARS_VERSION); if (!query.exec(versionQuery)) qCWarning(KSTARS) << query.lastError(); // Profiles if (!query.exec("CREATE TABLE IF NOT EXISTS profile (id INTEGER DEFAULT NULL PRIMARY KEY " "AUTOINCREMENT, name TEXT NOT NULL, host TEXT, port INTEGER, city TEXT, province TEXT, " "country TEXT, indiwebmanagerport INTEGER DEFAULT NULL)")) qCWarning(KSTARS) << query.lastError(); // Drivers if (!query.exec("CREATE TABLE IF NOT EXISTS driver (id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, " "label TEXT NOT NULL, role TEXT NOT NULL, profile INTEGER NOT NULL, FOREIGN " "KEY(profile) REFERENCES profile(id))")) qCWarning(KSTARS) << query.lastError(); // Custom Drivers //if (!query.exec("CREATE TABLE IF NOT EXISTS custom_driver (id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, drivers TEXT NOT NULL, profile INTEGER NOT NULL, FOREIGN KEY(profile) REFERENCES profile(id))")) //qDebug() << query.lastError(); // Add sample profile #ifdef Q_OS_WIN if (!query.exec("INSERT INTO profile (name, host, port) VALUES ('Simulators', 'localhost', 7624)")) qDebug() << query.lastError(); #else if (!query.exec("INSERT INTO profile (name) VALUES ('Simulators')")) qCWarning(KSTARS) << query.lastError(); #endif // Add sample profile drivers if (!query.exec("INSERT INTO driver (label, role, profile) VALUES ('Telescope Simulator', 'Mount', 1)")) qCWarning(KSTARS) << query.lastError(); if (!query.exec("INSERT INTO driver (label, role, profile) VALUES ('CCD Simulator', 'CCD', 1)")) qCWarning(KSTARS) << query.lastError(); if (!query.exec("INSERT INTO driver (label, role, profile) VALUES ('Focuser Simulator', 'Focuser', 1)")) qCWarning(KSTARS) << query.lastError(); } // If prior to 2.6.1 upgrade database for dark library tables if (currentDBVersion < "2.6.1") { QSqlQuery query(userdb_); QString versionQuery = QString("UPDATE Version SET Version='%1'").arg(KSTARS_VERSION); if (!query.exec(versionQuery)) qCWarning(KSTARS) << query.lastError(); // Dark Frame if (!query.exec("CREATE TABLE IF NOT EXISTS darkframe (id INTEGER DEFAULT NULL PRIMARY KEY " "AUTOINCREMENT, ccd TEXT NOT NULL, chip INTEGER DEFAULT 0, binX INTEGER, binY INTEGER, " "temperature REAL, duration REAL, filename TEXT NOT NULL, timestamp DATETIME DEFAULT " "CURRENT_TIMESTAMP)")) qCWarning(KSTARS) << query.lastError(); } // If prior to 2.7.3 upgrade database to add column for focus offset if (currentDBVersion < "2.7.3") { QSqlQuery query(userdb_); QString columnQuery = QString("ALTER TABLE filter ADD COLUMN Offset TEXT"); query.exec(columnQuery); } // If prior to 2.7.5 upgrade database to add column for autoconnect if (currentDBVersion < "2.7.5") { QSqlQuery query(userdb_); QString columnQuery = QString("ALTER TABLE profile ADD COLUMN autoconnect INTEGER"); query.exec(columnQuery); } // If prior to 2.7.6 upgrade database to add column for filter exposure if (currentDBVersion < "2.7.6") { QSqlQuery query(userdb_); QString columnQuery = QString("ALTER TABLE filter ADD COLUMN Exposure TEXT DEFAULT '1'"); query.exec(columnQuery); } // If prior to 2.7.9 upgrade database to add guider app selection if (currentDBVersion < "2.7.9") { QSqlQuery query(userdb_); QString columnQuery = QString("ALTER TABLE profile ADD COLUMN guidertype INTEGER DEFAULT 0"); query.exec(columnQuery); columnQuery = QString("ALTER TABLE profile ADD COLUMN guiderhost TEXT"); query.exec(columnQuery); columnQuery = QString("ALTER TABLE profile ADD COLUMN guiderport INTEGER"); query.exec(columnQuery); } // If prior to 2.8.0 upgrade database to add telescope selection if (currentDBVersion < "2.8.0") { QSqlQuery query(userdb_); QString columnQuery = QString("ALTER TABLE profile ADD COLUMN primaryscope INTEGER DEFAULT 0"); query.exec(columnQuery); columnQuery = QString("ALTER TABLE profile ADD COLUMN guidescope INTEGER DEFAULT 0"); query.exec(columnQuery); } // If prior to 2.8.2 upgrade database to add HIPS sources if (currentDBVersion < "2.8.2") { // To get all lists // http://alasky.unistra.fr/MocServer/query?hips_service_url=*&dataproduct_type=!catalog&dataproduct_type=!cube&&moc_sky_fraction=1&get=record QSqlQuery query(userdb_); query.exec("DROP TABLE hips"); if (!query.exec("CREATE TABLE hips (ID TEXT NOT NULL UNIQUE," "obs_title TEXT NOT NULL, obs_description TEXT NOT NULL, hips_order TEXT NOT NULL," "hips_frame TEXT NOT NULL, hips_tile_width TEXT NOT NULL, hips_tile_format TEXT NOT NULL," "hips_service_url TEXT NOT NULL, moc_sky_fraction TEXT NOT NULL)")) qCWarning(KSTARS) << query.lastError(); // Add default data else { if (!query.exec("INSERT INTO hips (ID, obs_title, obs_description, hips_order, hips_frame, hips_tile_width, hips_tile_format, hips_service_url, moc_sky_fraction)" "VALUES ('CDS/P/DSS2/color', 'DSS colored', 'Color composition generated by CDS. This HiPS survey is based on 2 others HiPS surveys," " respectively DSS2-red and DSS2-blue HiPS, both of them directly generated from original scanned plates downloaded" " from STScI site. The red component has been built from POSS-II F, AAO-SES,SR and SERC-ER plates. The blue component" " has been build from POSS-II J and SERC-J,EJ. The green component is based on the mean of other components. Three" " missing plates from red survey (253, 260, 359) has been replaced by pixels from the DSSColor STScI jpeg survey." " The 11 missing blue plates (mainly in galactic plane) have not been replaced (only red component).'," "'9', 'equatorial', '512', 'jpeg fits', 'http://alasky.u-strasbg.fr/DSS/DSSColor','1')")) qCWarning(KSTARS) << query.lastError(); if (!query.exec("INSERT INTO hips (ID, obs_title, obs_description, hips_order, hips_frame, hips_tile_width, hips_tile_format, hips_service_url, moc_sky_fraction)" "VALUES ('CDS/P/2MASS/color', '2MASS color J (1.23 microns), H (1.66 microns), K (2.16 microns)'," "'2MASS has uniformly scanned the entire sky in three near-infrared bands to detect and characterize point sources" " brighter than about 1 mJy in each band, with signal-to-noise ratio (SNR) greater than 10, using a pixel size of" " 2.0\". This has achieved an 80,000-fold improvement in sensitivity relative to earlier surveys. 2MASS used two" " highly-automated 1.3-m telescopes, one at Mt. Hopkins, AZ, and one at CTIO, Chile. Each telescope was equipped with" " a three-channel camera, each channel consisting of a 256x256 array of HgCdTe detectors, capable of observing the" " sky simultaneously at J (1.25 microns), H (1.65 microns), and Ks (2.17 microns). The University of Massachusetts" " (UMass) was responsible for the overall management of the project, and for developing the infrared cameras and" " on-site computing systems at both facilities. The Infrared Processing and Analysis Center (IPAC) is responsible" " for all data processing through the Production Pipeline, and construction and distribution of the data products." " Funding is provided primarily by NASA and the NSF'," "'9', 'equatorial', '512', 'jpeg fits', 'http://alaskybis.u-strasbg.fr/2MASS/Color', '1')")) qCWarning(KSTARS) << query.lastError(); if (!query.exec("INSERT INTO hips (ID, obs_title, obs_description, hips_order, hips_frame, hips_tile_width, hips_tile_format, hips_service_url, moc_sky_fraction)" "VALUES ('CDS/P/Fermi/color', 'Fermi Color HEALPix survey', 'Launched on June 11, 2008, the Fermi Gamma-ray Space Telescope observes the cosmos using the" " highest-energy form of light. This survey sums all data observed by the Fermi mission up to week 396. This version" " of the Fermi survey are intensity maps where the summed counts maps are divided by the exposure for each pixel" ". We anticipate using the HEASARC Hera capabilities to update this survey on a roughly quarterly basis. Data is" " broken into 5 energy bands : 30-100 MeV Band 1, 100-300 MeV Band 2, 300-1000 MeV Band 3, 1-3 GeV Band 4 ," " 3-300 GeV Band 5. The SkyView data are based upon a Cartesian projection of the counts divided by the exposure maps." " In the Cartesian projection pixels near the pole have a much smaller area than pixels on the equator, so these" " pixels have smaller integrated flux. When creating large scale images in other projections users may wish to make" " sure to compensate for this effect the flux conserving clip-resampling option.', '9', 'equatorial', '512', 'jpeg fits'," "'http://alaskybis.u-strasbg.fr/Fermi/Color', '1')")) qCWarning(KSTARS) << query.lastError(); } } // If prior to 2.8.3 drop filters if invalid if (currentDBVersion < "2.8.3") { QSqlQuery query(userdb_); if (!query.exec("PRAGMA table_info(filter)")) qCWarning(KSTARS) << query.lastError(); else { bool validTable = false; while (query.next()) { if (query.value(1) == "Exposure") { validTable = true; break; } } if (validTable == false) { qCWarning(KSTARS) << "Detected invalid filter table, re-creating..."; if (!query.exec("DROP table filter")) qCWarning(KSTARS) << query.lastError(); if (!query.exec("CREATE TABLE filter ( " "id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, " "Vendor TEXT DEFAULT NULL, " "Model TEXT DEFAULT NULL, " "Type TEXT DEFAULT NULL, " "Offset TEXT DEFAULT NULL, " "Color TEXT DEFAULT NULL," "Exposure TEXT DEFAULT '1')")) qCWarning(KSTARS) << query.lastError(); } } } } } userdb_.close(); return true; } QSqlError KSUserDB::LastError() { // error description is in QSqlError::text() return userdb_.lastError(); } bool KSUserDB::FirstRun() { if (!RebuildDB()) return false; /*ImportFlags(); ImportUsers(); ImportEquipment();*/ return true; } bool KSUserDB::RebuildDB() { qCInfo(KSTARS) << "Rebuilding User Database"; QVector tables; tables.append("CREATE TABLE Version (" "Version CHAR DEFAULT NULL)"); tables.append("INSERT INTO Version VALUES (\"" KSTARS_VERSION "\")"); tables.append("CREATE TABLE user ( " "id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, " "Name TEXT NOT NULL DEFAULT 'NULL', " "Surname TEXT NOT NULL DEFAULT 'NULL', " "Contact TEXT DEFAULT NULL)"); tables.append("CREATE TABLE telescope ( " "id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, " "Vendor TEXT DEFAULT NULL, " "Aperture REAL NOT NULL DEFAULT NULL, " "Model TEXT DEFAULT NULL, " "Driver TEXT DEFAULT NULL, " "Type TEXT DEFAULT NULL, " "FocalLength REAL DEFAULT NULL)"); tables.append("CREATE TABLE flags ( " "id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, " "RA TEXT NOT NULL DEFAULT NULL, " "Dec TEXT NOT NULL DEFAULT NULL, " "Icon TEXT NOT NULL DEFAULT 'NULL', " "Label TEXT NOT NULL DEFAULT 'NULL', " "Color TEXT DEFAULT NULL, " "Epoch TEXT DEFAULT NULL)"); tables.append("CREATE TABLE lens ( " "id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, " "Vendor TEXT NOT NULL DEFAULT 'NULL', " "Model TEXT DEFAULT NULL, " "Factor REAL NOT NULL DEFAULT NULL)"); tables.append("CREATE TABLE eyepiece ( " "id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, " "Vendor TEXT DEFAULT NULL, " "Model TEXT DEFAULT NULL, " "FocalLength REAL NOT NULL DEFAULT NULL, " "ApparentFOV REAL NOT NULL DEFAULT NULL, " "FOVUnit TEXT NOT NULL DEFAULT NULL)"); tables.append("CREATE TABLE filter ( " "id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, " "Vendor TEXT DEFAULT NULL, " "Model TEXT DEFAULT NULL, " "Type TEXT DEFAULT NULL, " "Offset TEXT DEFAULT NULL, " "Color TEXT DEFAULT NULL," "Exposure TEXT DEFAULT '1')"); tables.append("CREATE TABLE wishlist ( " "id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, " "Date NUMERIC NOT NULL DEFAULT NULL, " "Type TEXT DEFAULT NULL, " "UIUD TEXT DEFAULT NULL)"); tables.append("CREATE TABLE fov ( " "id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, " "name TEXT NOT NULL DEFAULT 'NULL', " "color TEXT DEFAULT NULL, " "sizeX NUMERIC DEFAULT NULL, " "sizeY NUMERIC DEFAULT NULL, " "shape TEXT DEFAULT NULL)"); tables.append("CREATE TABLE logentry ( " "id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, " "content TEXT NOT NULL DEFAULT 'NULL', " "UIUD TEXT DEFAULT NULL, " "DateTime NUMERIC NOT NULL DEFAULT NULL, " "User INTEGER DEFAULT NULL REFERENCES user (id), " "Location TEXT DEFAULT NULL, " "Telescope INTEGER DEFAULT NULL REFERENCES telescope (id)," "Filter INTEGER DEFAULT NULL REFERENCES filter (id), " "lens INTEGER DEFAULT NULL REFERENCES lens (id), " "Eyepiece INTEGER DEFAULT NULL REFERENCES eyepiece (id), " "FOV INTEGER DEFAULT NULL REFERENCES fov (id))"); tables.append("CREATE TABLE horizons ( " "id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, " "name TEXT NOT NULL," "label TEXT NOT NULL," "enabled INTEGER NOT NULL)"); tables.append("CREATE TABLE profile (id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, host " "TEXT, port INTEGER, city TEXT, province TEXT, country TEXT, indiwebmanagerport INTEGER DEFAULT " "NULL, autoconnect INTEGER DEFAULT 1, guidertype INTEGER DEFAULT 0, guiderhost TEXT, guiderport INTEGER, primaryscope INTEGER DEFAULT 0, guidescope INTEGER DEFAULT 0)"); tables.append("CREATE TABLE driver (id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, label TEXT NOT NULL, role " "TEXT NOT NULL, profile INTEGER NOT NULL, FOREIGN KEY(profile) REFERENCES profile(id))"); //tables.append("CREATE TABLE custom_driver (id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, drivers TEXT NOT NULL, profile INTEGER NOT NULL, FOREIGN KEY(profile) REFERENCES profile(id))"); #ifdef Q_OS_WIN tables.append("INSERT INTO profile (name, host, port) VALUES ('Simulators', 'localhost', 7624)"); #else tables.append("INSERT INTO profile (name) VALUES ('Simulators')"); #endif tables.append("INSERT INTO driver (label, role, profile) VALUES ('Telescope Simulator', 'Mount', 1)"); tables.append("INSERT INTO driver (label, role, profile) VALUES ('CCD Simulator', 'CCD', 1)"); tables.append("INSERT INTO driver (label, role, profile) VALUES ('Focuser Simulator', 'Focuser', 1)"); tables.append("CREATE TABLE IF NOT EXISTS darkframe (id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, ccd TEXT " "NOT NULL, chip INTEGER DEFAULT 0, binX INTEGER, binY INTEGER, temperature REAL, duration REAL, " "filename TEXT NOT NULL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)"); tables.append("CREATE TABLE IF NOT EXISTS hips (ID TEXT NOT NULL UNIQUE," "obs_title TEXT NOT NULL, obs_description TEXT NOT NULL, hips_order TEXT NOT NULL," "hips_frame TEXT NOT NULL, hips_tile_width TEXT NOT NULL, hips_tile_format TEXT NOT NULL," "hips_service_url TEXT NOT NULL, moc_sky_fraction TEXT NOT NULL)"); tables.append("INSERT INTO hips (ID, obs_title, obs_description, hips_order, hips_frame, hips_tile_width, hips_tile_format, hips_service_url, moc_sky_fraction)" "VALUES ('CDS/P/DSS2/color', 'DSS colored', 'Color composition generated by CDS. This HiPS survey is based on 2 others HiPS surveys," " respectively DSS2-red and DSS2-blue HiPS, both of them directly generated from original scanned plates downloaded" " from STScI site. The red component has been built from POSS-II F, AAO-SES,SR and SERC-ER plates. The blue component" " has been build from POSS-II J and SERC-J,EJ. The green component is based on the mean of other components. Three" " missing plates from red survey (253, 260, 359) has been replaced by pixels from the DSSColor STScI jpeg survey." " The 11 missing blue plates (mainly in galactic plane) have not been replaced (only red component).'," "'9', 'equatorial', '512', 'jpeg fits', 'http://alasky.u-strasbg.fr/DSS/DSSColor','1')"); tables.append("INSERT INTO hips (ID, obs_title, obs_description, hips_order, hips_frame, hips_tile_width, hips_tile_format, hips_service_url, moc_sky_fraction)" "VALUES ('CDS/P/2MASS/color', '2MASS color J (1.23 microns), H (1.66 microns), K (2.16 microns)'," "'2MASS has uniformly scanned the entire sky in three near-infrared bands to detect and characterize point sources" " brighter than about 1 mJy in each band, with signal-to-noise ratio (SNR) greater than 10, using a pixel size of" " 2.0\". This has achieved an 80,000-fold improvement in sensitivity relative to earlier surveys. 2MASS used two" " highly-automated 1.3-m telescopes, one at Mt. Hopkins, AZ, and one at CTIO, Chile. Each telescope was equipped with" " a three-channel camera, each channel consisting of a 256x256 array of HgCdTe detectors, capable of observing the" " sky simultaneously at J (1.25 microns), H (1.65 microns), and Ks (2.17 microns). The University of Massachusetts" " (UMass) was responsible for the overall management of the project, and for developing the infrared cameras and" " on-site computing systems at both facilities. The Infrared Processing and Analysis Center (IPAC) is responsible" " for all data processing through the Production Pipeline, and construction and distribution of the data products." " Funding is provided primarily by NASA and the NSF'," "'9', 'equatorial', '512', 'jpeg fits', 'http://alaskybis.u-strasbg.fr/2MASS/Color', '1')"); tables.append("INSERT INTO hips (ID, obs_title, obs_description, hips_order, hips_frame, hips_tile_width, hips_tile_format, hips_service_url, moc_sky_fraction)" "VALUES ('CDS/P/Fermi/color', 'Fermi Color HEALPix survey', 'Launched on June 11, 2008, the Fermi Gamma-ray Space Telescope observes the cosmos using the" " highest-energy form of light. This survey sums all data observed by the Fermi mission up to week 396. This version" " of the Fermi survey are intensity maps where the summed counts maps are divided by the exposure for each pixel" ". We anticipate using the HEASARC Hera capabilities to update this survey on a roughly quarterly basis. Data is" " broken into 5 energy bands : 30-100 MeV Band 1, 100-300 MeV Band 2, 300-1000 MeV Band 3, 1-3 GeV Band 4 ," " 3-300 GeV Band 5. The SkyView data are based upon a Cartesian projection of the counts divided by the exposure maps." " In the Cartesian projection pixels near the pole have a much smaller area than pixels on the equator, so these" " pixels have smaller integrated flux. When creating large scale images in other projections users may wish to make" " sure to compensate for this effect the flux conserving clip-resampling option.', '9', 'equatorial', '512', 'jpeg fits'," "'http://alaskybis.u-strasbg.fr/Fermi/Color', '1')"); for (int i = 0; i < tables.count(); ++i) { QSqlQuery query(userdb_); if (!query.exec(tables[i])) { qCDebug(KSTARS) << query.lastError(); qCDebug(KSTARS) << query.executedQuery(); } } return true; } /* * Observer Section */ void KSUserDB::AddObserver(const QString &name, const QString &surname, const QString &contact) { userdb_.open(); - QSqlTableModel users(0, userdb_); + QSqlTableModel users(nullptr, userdb_); users.setTable("user"); users.setFilter("Name LIKE \'" + name + "\' AND Surname LIKE \'" + surname + "\'"); users.select(); if (users.rowCount() > 0) { QSqlRecord record = users.record(0); record.setValue("Name", name); record.setValue("Surname", surname); record.setValue("Contact", contact); users.setRecord(0, record); users.submitAll(); } else { int row = 0; users.insertRows(row, 1); users.setData(users.index(row, 1), name); // row0 is autoincerement ID users.setData(users.index(row, 2), surname); users.setData(users.index(row, 3), contact); users.submitAll(); } userdb_.close(); } bool KSUserDB::FindObserver(const QString &name, const QString &surname) { userdb_.open(); - QSqlTableModel users(0, userdb_); + QSqlTableModel users(nullptr, userdb_); users.setTable("user"); users.setFilter("Name LIKE \'" + name + "\' AND Surname LIKE \'" + surname + "\'"); users.select(); int observer_count = users.rowCount(); users.clear(); userdb_.close(); return (observer_count > 0); } // TODO(spacetime): This method is currently unused. bool KSUserDB::DeleteObserver(const QString &id) { userdb_.open(); - QSqlTableModel users(0, userdb_); + QSqlTableModel users(nullptr, userdb_); users.setTable("user"); users.setFilter("id = \'" + id + "\'"); users.select(); users.removeRows(0, 1); users.submitAll(); int observer_count = users.rowCount(); users.clear(); userdb_.close(); return (observer_count > 0); } QSqlDatabase KSUserDB::GetDatabase() { userdb_.open(); return userdb_; } #ifndef KSTARS_LITE void KSUserDB::GetAllObservers(QList &observer_list) { userdb_.open(); observer_list.clear(); - QSqlTableModel users(0, userdb_); + QSqlTableModel users(nullptr, userdb_); users.setTable("user"); users.select(); for (int i = 0; i < users.rowCount(); ++i) { QSqlRecord record = users.record(i); QString id = record.value("id").toString(); QString name = record.value("Name").toString(); QString surname = record.value("Surname").toString(); QString contact = record.value("Contact").toString(); OAL::Observer *o = new OAL::Observer(id, name, surname, contact); observer_list.append(o); } users.clear(); userdb_.close(); } #endif /* Dark Library Section */ void KSUserDB::AddDarkFrame(const QVariantMap &oneFrame) { userdb_.open(); - QSqlTableModel darkframe(0, userdb_); + QSqlTableModel darkframe(nullptr, userdb_); darkframe.setTable("darkframe"); darkframe.select(); QSqlRecord record = darkframe.record(); // Remove PK so that it gets auto-incremented later record.remove(0); // Remove timestamp so that it gets auto-generated record.remove(7); for (QVariantMap::const_iterator iter = oneFrame.begin(); iter != oneFrame.end(); ++iter) record.setValue(iter.key(), iter.value()); darkframe.insertRecord(-1, record); darkframe.submitAll(); userdb_.close(); } bool KSUserDB::DeleteDarkFrame(const QString &filename) { userdb_.open(); - QSqlTableModel darkframe(0, userdb_); + QSqlTableModel darkframe(nullptr, userdb_); darkframe.setTable("darkframe"); darkframe.setFilter("filename = \'" + filename + "\'"); darkframe.select(); darkframe.removeRows(0, 1); darkframe.submitAll(); userdb_.close(); return true; } void KSUserDB::GetAllDarkFrames(QList &darkFrames) { darkFrames.clear(); userdb_.open(); - QSqlTableModel darkframe(0, userdb_); + QSqlTableModel darkframe(nullptr, userdb_); darkframe.setTable("darkframe"); darkframe.select(); for (int i = 0; i < darkframe.rowCount(); ++i) { QVariantMap recordMap; QSqlRecord record = darkframe.record(i); for (int j = 1; j < record.count(); j++) recordMap[record.fieldName(j)] = record.value(j); darkFrames.append(recordMap); } userdb_.close(); } /* HiPS Section */ void KSUserDB::AddHIPSSource(const QMap &oneSource) { userdb_.open(); - QSqlTableModel HIPSSource(0, userdb_); + QSqlTableModel HIPSSource(nullptr, userdb_); HIPSSource.setTable("hips"); HIPSSource.select(); QSqlRecord record = HIPSSource.record(); for (QMap::const_iterator iter = oneSource.begin(); iter != oneSource.end(); ++iter) record.setValue(iter.key(), iter.value()); HIPSSource.insertRecord(-1, record); HIPSSource.submitAll(); userdb_.close(); } bool KSUserDB::DeleteHIPSSource(const QString &ID) { userdb_.open(); - QSqlTableModel HIPSSource(0, userdb_); + QSqlTableModel HIPSSource(nullptr, userdb_); HIPSSource.setTable("hips"); HIPSSource.setFilter("ID = \'" + ID + "\'"); HIPSSource.select(); HIPSSource.removeRows(0, 1); HIPSSource.submitAll(); userdb_.close(); return true; } void KSUserDB::GetAllHIPSSources(QList> &HIPSSources) { HIPSSources.clear(); userdb_.open(); - QSqlTableModel HIPSSource(0, userdb_); + QSqlTableModel HIPSSource(nullptr, userdb_); HIPSSource.setTable("hips"); HIPSSource.select(); for (int i = 0; i < HIPSSource.rowCount(); ++i) { QMap recordMap; QSqlRecord record = HIPSSource.record(i); for (int j = 1; j < record.count(); j++) recordMap[record.fieldName(j)] = record.value(j).toString(); HIPSSources.append(recordMap); } userdb_.close(); } /* * Flag Section */ void KSUserDB::DeleteAllFlags() { userdb_.open(); - QSqlTableModel flags(0, userdb_); + QSqlTableModel flags(nullptr, userdb_); flags.setEditStrategy(QSqlTableModel::OnManualSubmit); flags.setTable("flags"); flags.select(); flags.removeRows(0, flags.rowCount()); flags.submitAll(); flags.clear(); userdb_.close(); } void KSUserDB::AddFlag(const QString &ra, const QString &dec, const QString &epoch, const QString &image_name, const QString &label, const QString &labelColor) { userdb_.open(); - QSqlTableModel flags(0, userdb_); + QSqlTableModel flags(nullptr, userdb_); flags.setTable("flags"); int row = 0; flags.insertRows(row, 1); flags.setData(flags.index(row, 1), ra); // row,0 is autoincerement ID flags.setData(flags.index(row, 2), dec); flags.setData(flags.index(row, 3), image_name); flags.setData(flags.index(row, 4), label); flags.setData(flags.index(row, 5), labelColor); flags.setData(flags.index(row, 6), epoch); flags.submitAll(); flags.clear(); userdb_.close(); } QList KSUserDB::GetAllFlags() { QList flagList; userdb_.open(); - QSqlTableModel flags(0, userdb_); + QSqlTableModel flags(nullptr, userdb_); flags.setTable("flags"); flags.select(); for (int i = 0; i < flags.rowCount(); ++i) { QStringList flagEntry; QSqlRecord record = flags.record(i); /* flagEntry order description * The variation in the order is due to variation * in flag entry description order and flag database * description order. * flag (database): ra, dec, icon, label, color, epoch * flag (object): ra, dec, epoch, icon, label, color */ flagEntry.append(record.value(1).toString()); flagEntry.append(record.value(2).toString()); flagEntry.append(record.value(6).toString()); flagEntry.append(record.value(3).toString()); flagEntry.append(record.value(4).toString()); flagEntry.append(record.value(5).toString()); flagList.append(flagEntry); } flags.clear(); userdb_.close(); return flagList; } /* * Generic Section */ void KSUserDB::DeleteEquipment(const QString &type, const int &id) { userdb_.open(); - QSqlTableModel equip(0, userdb_); + QSqlTableModel equip(nullptr, userdb_); equip.setTable(type); equip.setFilter("id = " + QString::number(id)); equip.select(); equip.removeRows(0, equip.rowCount()); equip.submitAll(); equip.clear(); userdb_.close(); } void KSUserDB::DeleteAllEquipment(const QString &type) { userdb_.open(); - QSqlTableModel equip(0, userdb_); + QSqlTableModel equip(nullptr, userdb_); equip.setEditStrategy(QSqlTableModel::OnManualSubmit); equip.setTable(type); equip.setFilter("id >= 1"); equip.select(); equip.removeRows(0, equip.rowCount()); equip.submitAll(); equip.clear(); userdb_.close(); } /* * Telescope section */ void KSUserDB::AddScope(const QString &model, const QString &vendor, const QString &driver, const QString &type, const double &focalLength, const double &aperture) { userdb_.open(); - QSqlTableModel equip(0, userdb_); + QSqlTableModel equip(nullptr, userdb_); equip.setTable("telescope"); int row = 0; equip.insertRows(row, 1); equip.setData(equip.index(row, 1), vendor); // row,0 is autoincerement ID equip.setData(equip.index(row, 2), aperture); equip.setData(equip.index(row, 3), model); equip.setData(equip.index(row, 4), driver); equip.setData(equip.index(row, 5), type); equip.setData(equip.index(row, 6), focalLength); equip.submitAll(); equip.clear(); //DB will not close if linked object not cleared userdb_.close(); } void KSUserDB::AddScope(const QString &model, const QString &vendor, const QString &driver, const QString &type, const double &focalLength, const double &aperture, const QString &id) { userdb_.open(); - QSqlTableModel equip(0, userdb_); + QSqlTableModel equip(nullptr, userdb_); equip.setTable("telescope"); equip.setFilter("id = " + id); equip.select(); if (equip.rowCount() > 0) { QSqlRecord record = equip.record(0); record.setValue(1, vendor); record.setValue(2, aperture); record.setValue(3, model); record.setValue(4, driver); record.setValue(5, type); record.setValue(6, focalLength); equip.setRecord(0, record); equip.submitAll(); } userdb_.close(); } #ifndef KSTARS_LITE void KSUserDB::GetAllScopes(QList &scope_list) { scope_list.clear(); userdb_.open(); - QSqlTableModel equip(0, userdb_); + QSqlTableModel equip(nullptr, userdb_); equip.setTable("telescope"); equip.select(); for (int i = 0; i < equip.rowCount(); ++i) { QSqlRecord record = equip.record(i); QString id = record.value("id").toString(); QString vendor = record.value("Vendor").toString(); double aperture = record.value("Aperture").toDouble(); QString model = record.value("Model").toString(); QString driver = record.value("Driver").toString(); QString type = record.value("Type").toString(); double focalLength = record.value("FocalLength").toDouble(); OAL::Scope *o = new OAL::Scope(id, model, vendor, type, focalLength, aperture); o->setINDIDriver(driver); scope_list.append(o); } equip.clear(); userdb_.close(); } #endif /* * Eyepiece section */ void KSUserDB::AddEyepiece(const QString &vendor, const QString &model, const double &focalLength, const double &fov, const QString &fovunit) { userdb_.open(); - QSqlTableModel equip(0, userdb_); + QSqlTableModel equip(nullptr, userdb_); equip.setTable("eyepiece"); int row = 0; equip.insertRows(row, 1); equip.setData(equip.index(row, 1), vendor); // row,0 is autoincerement ID equip.setData(equip.index(row, 2), model); equip.setData(equip.index(row, 3), focalLength); equip.setData(equip.index(row, 4), fov); equip.setData(equip.index(row, 5), fovunit); equip.submitAll(); equip.clear(); userdb_.close(); } void KSUserDB::AddEyepiece(const QString &vendor, const QString &model, const double &focalLength, const double &fov, const QString &fovunit, const QString &id) { userdb_.open(); - QSqlTableModel equip(0, userdb_); + QSqlTableModel equip(nullptr, userdb_); equip.setTable("eyepiece"); equip.setFilter("id = " + id); equip.select(); if (equip.rowCount() > 0) { QSqlRecord record = equip.record(0); record.setValue(1, vendor); record.setValue(2, model); record.setValue(3, focalLength); record.setValue(4, fov); record.setValue(5, fovunit); equip.setRecord(0, record); equip.submitAll(); } userdb_.close(); } #ifndef KSTARS_LITE void KSUserDB::GetAllEyepieces(QList &eyepiece_list) { eyepiece_list.clear(); userdb_.open(); - QSqlTableModel equip(0, userdb_); + QSqlTableModel equip(nullptr, userdb_); equip.setTable("eyepiece"); equip.select(); for (int i = 0; i < equip.rowCount(); ++i) { QSqlRecord record = equip.record(i); QString id = record.value("id").toString(); QString vendor = record.value("Vendor").toString(); QString model = record.value("Model").toString(); double focalLength = record.value("FocalLength").toDouble(); double fov = record.value("ApparentFOV").toDouble(); QString fovUnit = record.value("FOVUnit").toString(); OAL::Eyepiece *o = new OAL::Eyepiece(id, model, vendor, fov, fovUnit, focalLength); eyepiece_list.append(o); } equip.clear(); userdb_.close(); } #endif /* * lens section */ void KSUserDB::AddLens(const QString &vendor, const QString &model, const double &factor) { userdb_.open(); - QSqlTableModel equip(0, userdb_); + QSqlTableModel equip(nullptr, userdb_); equip.setTable("lens"); int row = 0; equip.insertRows(row, 1); equip.setData(equip.index(row, 1), vendor); // row,0 is autoincerement ID equip.setData(equip.index(row, 2), model); equip.setData(equip.index(row, 3), factor); equip.submitAll(); equip.clear(); userdb_.close(); } void KSUserDB::AddLens(const QString &vendor, const QString &model, const double &factor, const QString &id) { userdb_.open(); - QSqlTableModel equip(0, userdb_); + QSqlTableModel equip(nullptr, userdb_); equip.setTable("lens"); equip.setFilter("id = " + id); equip.select(); if (equip.rowCount() > 0) { QSqlRecord record = equip.record(0); record.setValue(1, vendor); record.setValue(2, model); record.setValue(3, factor); equip.submitAll(); } userdb_.close(); } #ifndef KSTARS_LITE void KSUserDB::GetAllLenses(QList &lens_list) { lens_list.clear(); userdb_.open(); - QSqlTableModel equip(0, userdb_); + QSqlTableModel equip(nullptr, userdb_); equip.setTable("lens"); equip.select(); for (int i = 0; i < equip.rowCount(); ++i) { QSqlRecord record = equip.record(i); QString id = record.value("id").toString(); QString vendor = record.value("Vendor").toString(); QString model = record.value("Model").toString(); double factor = record.value("Factor").toDouble(); OAL::Lens *o = new OAL::Lens(id, model, vendor, factor); lens_list.append(o); } equip.clear(); userdb_.close(); } #endif /* * filter section */ void KSUserDB::AddFilter(const QString &vendor, const QString &model, const QString &type, const QString &offset, const QString &color, const QString &exposure) { userdb_.open(); - QSqlTableModel equip(0, userdb_); + QSqlTableModel equip(nullptr, userdb_); equip.setTable("filter"); int row = 0; equip.insertRows(row, 1); equip.setData(equip.index(row, 1), vendor); // row,0 is autoincerement ID equip.setData(equip.index(row, 2), model); equip.setData(equip.index(row, 3), type); equip.setData(equip.index(row, 4), offset); equip.setData(equip.index(row, 5), color); equip.setData(equip.index(row, 6), exposure); if (equip.submitAll() == false) qCritical() << "AddFilter:" << equip.lastError(); equip.clear(); userdb_.close(); } void KSUserDB::AddFilter(const QString &vendor, const QString &model, const QString &type, const QString &offset, const QString &color, const QString &exposure, const QString &id) { userdb_.open(); - QSqlTableModel equip(0, userdb_); + QSqlTableModel equip(nullptr, userdb_); equip.setTable("filter"); equip.setFilter("id = " + id); equip.select(); if (equip.rowCount() > 0) { QSqlRecord record = equip.record(0); record.setValue(1, vendor); record.setValue(2, model); record.setValue(3, type); record.setValue(4, offset); record.setValue(5, color); record.setValue(6, exposure); equip.setRecord(0, record); if (equip.submitAll() == false) qCritical() << "AddFilter:" << equip.lastError(); } userdb_.close(); } #ifndef KSTARS_LITE void KSUserDB::GetAllFilters(QList &filter_list) { userdb_.open(); filter_list.clear(); - QSqlTableModel equip(0, userdb_); + QSqlTableModel equip(nullptr, userdb_); equip.setTable("filter"); equip.select(); for (int i = 0; i < equip.rowCount(); ++i) { QSqlRecord record = equip.record(i); QString id = record.value("id").toString(); QString vendor = record.value("Vendor").toString(); QString model = record.value("Model").toString(); QString type = record.value("Type").toString(); QString color = record.value("Color").toString(); QString offset = record.value("Offset").toString(); QString exposure = record.value("Exposure").toString(); OAL::Filter *o = new OAL::Filter(id, model, vendor, type, offset, color, exposure); filter_list.append(o); } equip.clear(); userdb_.close(); return; } #endif #if 0 bool KSUserDB::ImportFlags() { QString flagfilename = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + "flags.dat"; QFile flagsfile(flagfilename); if (!flagsfile.exists()) { return false; // No upgrade needed. Flags file doesn't exist. } QList< QPair > flag_file_sequence; flag_file_sequence.append(qMakePair(QString("RA"), KSParser::D_QSTRING)); flag_file_sequence.append(qMakePair(QString("Dec"), KSParser::D_QSTRING)); flag_file_sequence.append(qMakePair(QString("epoch"), KSParser::D_QSTRING)); flag_file_sequence.append(qMakePair(QString("icon"), KSParser::D_QSTRING)); flag_file_sequence.append(qMakePair(QString("label"), KSParser::D_QSTRING)); flag_file_sequence.append(qMakePair(QString("color"), KSParser::D_QSTRING)); KSParser flagparser(flagfilename,'#',flag_file_sequence,' '); QHash row_content; while (flagparser.HasNextRow()) { row_content = flagparser.ReadNextRow(); QString ra = row_content["RA"].toString(); QString dec = row_content["Dec"].toString(); QString epoch = row_content["epoch"].toString(); QString icon = row_content["icon"].toString(); QString label = row_content["label"].toString(); QString color = row_content["color"].toString(); AddFlag(ra,dec,epoch,icon,label,color); } return true; } bool KSUserDB::ImportUsers() { QString usersfilename = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + "observerlist.xml"; QFile usersfile(usersfilename); if (!usersfile.exists()) { return false; // No upgrade needed. Users file doesn't exist. } if( ! usersfile.open( QIODevice::ReadOnly ) ) return false; QXmlStreamReader * reader = new QXmlStreamReader(&usersfile); while( ! reader->atEnd() ) { reader->readNext(); if( reader->isEndElement() ) break; if( reader->isStartElement() ) { if (reader->name() != "observers") continue; //Read all observers while( ! reader->atEnd() ) { reader->readNext(); if( reader->isEndElement() ) break; if( reader->isStartElement() ) { // Read single observer if( reader->name() == "observer" ) { QString name, surname, contact; while( ! reader->atEnd() ) { reader->readNext(); if( reader->isEndElement() ) break; if( reader->isStartElement() ) { if( reader->name() == "name" ) { name = reader->readElementText(); } else if( reader->name() == "surname" ) { surname = reader->readElementText(); } else if( reader->name() == "contact" ) { contact = reader->readElementText(); } } } AddObserver(name, surname, contact); } } } } } delete reader_; usersfile.close(); return true; } bool KSUserDB::ImportEquipment() { QString equipfilename = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + "equipmentlist.xml"; QFile equipfile(equipfilename); if (!equipfile.exists()) { return false; // No upgrade needed. File doesn't exist. } if( ! equipfile.open( QIODevice::ReadOnly ) ) return false; reader_ = new QXmlStreamReader(&equipfile); while( ! reader_->atEnd() ) { reader_->readNext(); if( reader_->isStartElement() ) { while( ! reader_->atEnd() ) { reader_->readNext(); if( reader_->isEndElement() ) break; if( reader_->isStartElement() ) { if( reader_->name() == "scopes" ) readScopes(); else if( reader_->name() == "eyepieces" ) readEyepieces(); else if( reader_->name() =="lenses" ) readLenses(); else if( reader_->name() =="filters" ) readFilters(); } } } } delete reader_; equipfile.close(); return true; } #endif void KSUserDB::readScopes() { while (!reader_->atEnd()) { reader_->readNext(); if (reader_->isEndElement()) break; if (reader_->isStartElement()) { if (reader_->name() == "scope") readScope(); } } } void KSUserDB::readEyepieces() { while (!reader_->atEnd()) { reader_->readNext(); if (reader_->isEndElement()) break; if (reader_->isStartElement()) { if (reader_->name() == "eyepiece") readEyepiece(); } } } void KSUserDB::readLenses() { while (!reader_->atEnd()) { reader_->readNext(); if (reader_->isEndElement()) break; if (reader_->isStartElement()) { if (reader_->name() == "lens") readLens(); } } } void KSUserDB::readFilters() { while (!reader_->atEnd()) { reader_->readNext(); if (reader_->isEndElement()) break; if (reader_->isStartElement()) { if (reader_->name() == "filter") readFilter(); } } } void KSUserDB::readScope() { QString model, vendor, type, driver = i18nc("No driver", "None"); double aperture = 0, focalLength = 0; while (!reader_->atEnd()) { reader_->readNext(); if (reader_->isEndElement()) break; if (reader_->isStartElement()) { if (reader_->name() == "model") { model = reader_->readElementText(); } else if (reader_->name() == "vendor") { vendor = reader_->readElementText(); } else if (reader_->name() == "type") { type = reader_->readElementText(); if (type == "N") type = "Newtonian"; if (type == "R") type = "Refractor"; if (type == "M") type = "Maksutov"; if (type == "S") type = "Schmidt-Cassegrain"; if (type == "K") type = "Kutter (Schiefspiegler)"; if (type == "C") type = "Cassegrain"; } else if (reader_->name() == "focalLength") { focalLength = (reader_->readElementText()).toDouble(); } else if (reader_->name() == "aperture") aperture = (reader_->readElementText()).toDouble(); else if (reader_->name() == "driver") driver = reader_->readElementText(); } } AddScope(model, vendor, driver, type, focalLength, aperture); } void KSUserDB::readEyepiece() { QString model, focalLength, vendor, fov, fovUnit; while (!reader_->atEnd()) { reader_->readNext(); if (reader_->isEndElement()) break; if (reader_->isStartElement()) { if (reader_->name() == "model") { model = reader_->readElementText(); } else if (reader_->name() == "vendor") { vendor = reader_->readElementText(); } else if (reader_->name() == "apparentFOV") { fov = reader_->readElementText(); fovUnit = reader_->attributes().value("unit").toString(); } else if (reader_->name() == "focalLength") { focalLength = reader_->readElementText(); } } } AddEyepiece(vendor, model, focalLength.toDouble(), fov.toDouble(), fovUnit); } void KSUserDB::readLens() { QString model, factor, vendor; while (!reader_->atEnd()) { reader_->readNext(); if (reader_->isEndElement()) break; if (reader_->isStartElement()) { if (reader_->name() == "model") { model = reader_->readElementText(); } else if (reader_->name() == "vendor") { vendor = reader_->readElementText(); } else if (reader_->name() == "factor") { factor = reader_->readElementText(); } } } AddLens(vendor, model, factor.toDouble()); } void KSUserDB::readFilter() { QString model, vendor, type, offset, color, exposure; while (!reader_->atEnd()) { reader_->readNext(); if (reader_->isEndElement()) break; if (reader_->isStartElement()) { if (reader_->name() == "model") { model = reader_->readElementText(); } else if (reader_->name() == "vendor") { vendor = reader_->readElementText(); } else if (reader_->name() == "type") { type = reader_->readElementText(); } else if (reader_->name() == "offset") { offset = reader_->readElementText(); } else if (reader_->name() == "color") { color = reader_->readElementText(); } else if (reader_->name() == "exposure") { exposure = reader_->readElementText(); } } } AddFilter(vendor, model, type, offset, color, exposure); } QList KSUserDB::GetAllHorizons() { QList horizonList; userdb_.open(); - QSqlTableModel regions(0, userdb_); + QSqlTableModel regions(nullptr, userdb_); regions.setTable("horizons"); regions.select(); - QSqlTableModel points(0, userdb_); + QSqlTableModel points(nullptr, userdb_); for (int i = 0; i < regions.rowCount(); ++i) { QSqlRecord record = regions.record(i); QString regionTable = record.value("name").toString(); QString regionName = record.value("label").toString(); bool enabled = record.value("enabled").toInt() == 1 ? true : false; points.setTable(regionTable); points.select(); std::shared_ptr skyList(new LineList()); ArtificialHorizonEntity *horizon = new ArtificialHorizonEntity; horizon->setRegion(regionName); horizon->setEnabled(enabled); horizon->setList(skyList); horizonList.append(horizon); for (int j = 0; j < points.rowCount(); j++) { std::shared_ptr p(new SkyPoint()); record = points.record(j); p->setAz(record.value(0).toDouble()); p->setAlt(record.value(1).toDouble()); p->HorizontalToEquatorial(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat()); skyList->append(std::move(p)); } points.clear(); } regions.clear(); userdb_.close(); return horizonList; } void KSUserDB::DeleteAllHorizons() { userdb_.open(); - QSqlTableModel regions(0, userdb_); + QSqlTableModel regions(nullptr, userdb_); regions.setEditStrategy(QSqlTableModel::OnManualSubmit); regions.setTable("horizons"); regions.select(); QSqlQuery query(userdb_); for (int i = 0; i < regions.rowCount(); ++i) { QSqlRecord record = regions.record(i); QString tableQuery = QString("DROP TABLE %1").arg(record.value("name").toString()); if (!query.exec(tableQuery)) qCWarning(KSTARS) << query.lastError().text(); } regions.removeRows(0, regions.rowCount()); regions.submitAll(); regions.clear(); userdb_.close(); } void KSUserDB::AddHorizon(ArtificialHorizonEntity *horizon) { userdb_.open(); - QSqlTableModel regions(0, userdb_); + QSqlTableModel regions(nullptr, userdb_); regions.setTable("horizons"); regions.select(); QString tableName = QString("horizon_%1").arg(regions.rowCount() + 1); regions.insertRow(0); regions.setData(regions.index(0, 1), tableName); regions.setData(regions.index(0, 2), horizon->region()); regions.setData(regions.index(0, 3), horizon->enabled() ? 1 : 0); regions.submitAll(); regions.clear(); QString tableQuery = QString("CREATE TABLE %1 (Az REAL NOT NULL, Alt REAL NOT NULL)").arg(tableName); QSqlQuery query(userdb_); query.exec(tableQuery); - QSqlTableModel points(0, userdb_); + QSqlTableModel points(nullptr, userdb_); points.setTable(tableName); SkyList *skyList = horizon->list()->points(); for (int i = 0; i < skyList->size(); i++) { points.select(); QSqlRecord rec(points.record()); rec.setValue("Az", skyList->at(i)->az().Degrees()); rec.setValue("Alt", skyList->at(i)->alt().Degrees()); points.insertRecord(-1, rec); } points.submitAll(); points.clear(); userdb_.close(); } int KSUserDB::AddProfile(const QString &name) { userdb_.open(); int id = -1; QSqlQuery query(userdb_); bool rc = query.exec(QString("INSERT INTO profile (name) VALUES('%1')").arg(name)); if (rc == false) qCWarning(KSTARS) << query.lastQuery() << query.lastError().text(); else id = query.lastInsertId().toInt(); userdb_.close(); return id; } bool KSUserDB::DeleteProfile(ProfileInfo *pi) { userdb_.open(); QSqlQuery query(userdb_); bool rc; rc = query.exec("DELETE FROM profile WHERE id=" + QString::number(pi->id)); if (rc == false) qCWarning(KSTARS) << query.lastQuery() << query.lastError().text(); userdb_.close(); return rc; } void KSUserDB::SaveProfile(ProfileInfo *pi) { // Remove all drivers DeleteProfileDrivers(pi); userdb_.open(); QSqlQuery query(userdb_); // Clear data if (!query.exec(QString("UPDATE profile SET " "host=null,port=null,city=null,province=null,country=null,indiwebmanagerport=NULL," "autoconnect=NULL,primaryscope=0,guidescope=0 WHERE id=%1") .arg(pi->id))) qCWarning(KSTARS) << query.executedQuery() << query.lastError().text(); // Update Name if (!query.exec(QString("UPDATE profile SET name='%1' WHERE id=%2").arg(pi->name).arg(pi->id))) qCWarning(KSTARS) << query.executedQuery() << query.lastError().text(); // Update Remote Data if (pi->host.isEmpty() == false) { if (!query.exec( QString("UPDATE profile SET host='%1',port=%2 WHERE id=%3").arg(pi->host).arg((pi->port)).arg(pi->id))) qCWarning(KSTARS) << query.executedQuery() << query.lastError().text(); if (pi->INDIWebManagerPort != -1) { if (!query.exec(QString("UPDATE profile SET indiwebmanagerport='%1' WHERE id=%2") .arg(pi->INDIWebManagerPort) .arg(pi->id))) qCWarning(KSTARS) << query.executedQuery() << query.lastError().text(); } } // Update City Info if (pi->city.isEmpty() == false) { if (!query.exec(QString("UPDATE profile SET city='%1',province='%2',country='%3' WHERE id=%4") .arg(pi->city, pi->province, pi->country) .arg(pi->id))) { qCWarning(KSTARS) << query.executedQuery() << query.lastError().text(); } } // Update Auto Connect Info if (!query.exec(QString("UPDATE profile SET autoconnect=%1 WHERE id=%2").arg(pi->autoConnect ? 1 : 0).arg(pi->id))) qCWarning(KSTARS) << query.executedQuery() << query.lastError().text(); // Update Guide Application Info if (!query.exec(QString("UPDATE profile SET guidertype=%1 WHERE id=%2").arg(pi->guidertype).arg(pi->id))) qCWarning(KSTARS) << query.executedQuery() << query.lastError().text(); // If using external guider if (pi->guidertype != 0) { if (!query.exec(QString("UPDATE profile SET guiderhost='%1' WHERE id=%2").arg(pi->guiderhost).arg(pi->id))) qCWarning(KSTARS) << query.executedQuery() << query.lastError().text(); if (!query.exec(QString("UPDATE profile SET guiderport=%1 WHERE id=%2").arg(pi->guiderport).arg(pi->id))) qCWarning(KSTARS) << query.executedQuery() << query.lastError().text(); } // Update scope selection if (!query.exec(QString("UPDATE profile SET primaryscope='%1' WHERE id=%2").arg(pi->primaryscope).arg(pi->id))) qCWarning(KSTARS) << query.executedQuery() << query.lastError().text(); if (!query.exec(QString("UPDATE profile SET guidescope=%1 WHERE id=%2").arg(pi->guidescope).arg(pi->id))) qCWarning(KSTARS) << query.executedQuery() << query.lastError().text(); QMapIterator i(pi->drivers); while (i.hasNext()) { i.next(); if (!query.exec(QString("INSERT INTO driver (label, role, profile) VALUES('%1','%2',%3)") .arg(i.value(), i.key()) .arg(pi->id))) { qCWarning(KSTARS) << query.executedQuery() << query.lastError().text(); } } /*if (pi->customDrivers.isEmpty() == false && !query.exec(QString("INSERT INTO custom_driver (drivers, profile) VALUES('%1',%2)").arg(pi->customDrivers).arg(pi->id))) qDebug() << query.lastQuery() << query.lastError().text();*/ userdb_.close(); } void KSUserDB::GetAllProfiles(QList> &profiles) { userdb_.open(); - QSqlTableModel profile(0, userdb_); + QSqlTableModel profile(nullptr, userdb_); profile.setTable("profile"); profile.select(); for (int i = 0; i < profile.rowCount(); ++i) { QSqlRecord record = profile.record(i); int id = record.value("id").toInt(); QString name = record.value("name").toString(); std::shared_ptr pi(new ProfileInfo(id, name)); // Add host and port pi->host = record.value("host").toString(); pi->port = record.value("port").toInt(); // City info pi->city = record.value("city").toString(); pi->province = record.value("province").toString(); pi->country = record.value("country").toString(); pi->INDIWebManagerPort = record.value("indiwebmanagerport").toInt(); pi->autoConnect = (record.value("autoconnect").toInt() == 1); pi->guidertype = record.value("guidertype").toInt(); if (pi->guidertype != 0) { pi->guiderhost = record.value("guiderhost").toString(); pi->guiderport = record.value("guiderport").toInt(); } pi->primaryscope = record.value("primaryscope").toInt(); pi->guidescope = record.value("guidescope").toInt(); GetProfileDrivers(pi.get()); //GetProfileCustomDrivers(pi); profiles.append(pi); } profile.clear(); userdb_.close(); } void KSUserDB::GetProfileDrivers(ProfileInfo *pi) { userdb_.open(); - QSqlTableModel driver(0, userdb_); + QSqlTableModel driver(nullptr, userdb_); driver.setTable("driver"); driver.setFilter("profile=" + QString::number(pi->id)); if (driver.select() == false) qCWarning(KSTARS) << "Driver select error:" << driver.lastError().text(); for (int i = 0; i < driver.rowCount(); ++i) { QSqlRecord record = driver.record(i); QString label = record.value("label").toString(); QString role = record.value("role").toString(); pi->drivers[role] = label; } driver.clear(); userdb_.close(); } /*void KSUserDB::GetProfileCustomDrivers(ProfileInfo* pi) { userdb_.open(); QSqlTableModel custom_driver(0, userdb_); custom_driver.setTable("driver"); custom_driver.setFilter("profile=" + QString::number(pi->id)); if (custom_driver.select() == false) qDebug() << "custom driver select error: " << custom_driver.query().lastQuery() << custom_driver.lastError().text(); QSqlRecord record = custom_driver.record(0); pi->customDrivers = record.value("drivers").toString(); custom_driver.clear(); userdb_.close(); }*/ void KSUserDB::DeleteProfileDrivers(ProfileInfo *pi) { userdb_.open(); QSqlQuery query(userdb_); /*if (!query.exec("DELETE FROM custom_driver WHERE profile=" + QString::number(pi->id))) qDebug() << query.lastQuery() << query.lastError().text();*/ if (!query.exec("DELETE FROM driver WHERE profile=" + QString::number(pi->id))) qCWarning(KSTARS) << query.executedQuery() << query.lastError().text(); userdb_.close(); } diff --git a/kstars/auxiliary/ksutils.cpp b/kstars/auxiliary/ksutils.cpp index b9734a031..42648c801 100644 --- a/kstars/auxiliary/ksutils.cpp +++ b/kstars/auxiliary/ksutils.cpp @@ -1,1254 +1,1254 @@ /*************************************************************************** ksutils.cpp - K Desktop Planetarium ------------------- begin : Mon Jan 7 10:48:09 EST 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. * * * ***************************************************************************/ #include "ksutils.h" #include "deepskyobject.h" #ifndef KSTARS_LITE #include "kswizard.h" #endif #include "Options.h" #include "starobject.h" #include "auxiliary/kspaths.h" #ifndef KSTARS_LITE #include #endif #include #include #include namespace KSUtils { bool openDataFile(QFile &file, const QString &s) { QString FileName = KSPaths::locate(QStandardPaths::GenericDataLocation, s); if (!FileName.isNull()) { file.setFileName(FileName); return file.open(QIODevice::ReadOnly); } return false; } QString getDSSURL(const SkyPoint *const p) { - const DeepSkyObject *dso = 0; + const DeepSkyObject *dso = nullptr; double height, width; double dss_default_size = Options::defaultDSSImageSize(); double dss_padding = Options::dSSPadding(); Q_ASSERT(p); Q_ASSERT(dss_default_size > 0.0 && dss_padding >= 0.0); dso = dynamic_cast(p); // Decide what to do about the height and width if (dso) { // For deep-sky objects, use their height and width information double a, b, pa; a = dso->a(); b = dso->a() * dso->e(); // Use a * e instead of b, since e() returns 1 whenever one of the dimensions is zero. This is important for circular objects pa = dso->pa() * dms::DegToRad; // We now want to convert a, b, and pa into an image // height and width -- i.e. a dRA and dDec. // DSS uses dDec for height and dRA for width. (i.e. "top" is north in the DSS images, AFAICT) // From some trigonometry, assuming we have a rectangular object (worst case), we need: width = a * sin(pa) + b * cos(pa); height = a * cos(pa) + b * sin(pa); // 'a' and 'b' are in arcminutes, so height and width are in arcminutes // Pad the RA and Dec, so that we show more of the sky than just the object. height += dss_padding; width += dss_padding; } else { // For a generic sky object, we don't know what to do. So // we just assume the default size. height = width = dss_default_size; } // There's no point in tiny DSS images that are smaller than dss_default_size if (height < dss_default_size) height = dss_default_size; if (width < dss_default_size) width = dss_default_size; return getDSSURL(p->ra0(), p->dec0(), width, height); } QString getDSSURL(const dms &ra, const dms &dec, float width, float height, const QString &type) { const QString URLprefix("http://archive.stsci.edu/cgi-bin/dss_search?"); QString URLsuffix = QString("&e=J2000&f=%1&c=none&fov=NONE").arg(type); const double dss_default_size = Options::defaultDSSImageSize(); char decsgn = (dec.Degrees() < 0.0) ? '-' : '+'; int dd = abs(dec.degree()); int dm = abs(dec.arcmin()); int ds = abs(dec.arcsec()); // Infinite and NaN sizes are replaced by the default size and tiny DSS images are resized to default size if (!qIsFinite(height) || height <= 0.0) height = dss_default_size; if (!qIsFinite(width) || width <= 0.0) width = dss_default_size; // DSS accepts images that are no larger than 75 arcminutes if (height > 75.0) height = 75.0; if (width > 75.0) width = 75.0; QString RAString, DecString, SizeString; DecString = DecString.sprintf("&d=%c%02d+%02d+%02d", decsgn, dd, dm, ds); RAString = RAString.sprintf("r=%02d+%02d+%02d", ra.hour(), ra.minute(), ra.second()); SizeString = SizeString.sprintf("&h=%02.1f&w=%02.1f", height, width); return (URLprefix + RAString + DecString + SizeString + URLsuffix); } QString toDirectionString(dms angle) { // TODO: Instead of doing it this way, it would be nicer to // compute the string to arbitrary precision. Although that will // not be easy to localize. (Consider, for instance, Indian // languages that have special names for the intercardinal points) // -- asimha static const char *directions[] = { I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "N"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "NNE"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "NE"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "ENE"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "E"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "ESE"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "SE"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "SSE"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "S"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "SSW"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "SW"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "WSW"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "W"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "WNW"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "NW"), I18N_NOOP2("Abbreviated cardinal / intercardinal etc. direction", "NNW"), I18N_NOOP2("Unknown cardinal / intercardinal direction", "???") }; int index = (int)((angle.reduce().Degrees() + 11.25) / 22.5); // A number between 0 and 16 (inclusive) is expected if (index < 0 || index > 16) index = 16; // Something went wrong. else index = (index == 16 ? 0 : index); return i18nc("Abbreviated cardinal / intercardinal etc. direction", directions[index]); } QList *castStarObjListToSkyObjList(QList *starObjList) { QList *skyObjList = new QList(); foreach (StarObject *so, *starObjList) { skyObjList->append(so); } return skyObjList; } QString constGenetiveFromAbbrev(const QString &code) { if (code == "And") return QString("Andromedae"); if (code == "Ant") return QString("Antliae"); if (code == "Aps") return QString("Apodis"); if (code == "Aqr") return QString("Aquarii"); if (code == "Aql") return QString("Aquilae"); if (code == "Ara") return QString("Arae"); if (code == "Ari") return QString("Arietis"); if (code == "Aur") return QString("Aurigae"); if (code == "Boo") return QString("Bootis"); if (code == "Cae") return QString("Caeli"); if (code == "Cam") return QString("Camelopardalis"); if (code == "Cnc") return QString("Cancri"); if (code == "CVn") return QString("Canum Venaticorum"); if (code == "CMa") return QString("Canis Majoris"); if (code == "CMi") return QString("Canis Minoris"); if (code == "Cap") return QString("Capricorni"); if (code == "Car") return QString("Carinae"); if (code == "Cas") return QString("Cassiopeiae"); if (code == "Cen") return QString("Centauri"); if (code == "Cep") return QString("Cephei"); if (code == "Cet") return QString("Ceti"); if (code == "Cha") return QString("Chamaeleontis"); if (code == "Cir") return QString("Circini"); if (code == "Col") return QString("Columbae"); if (code == "Com") return QString("Comae Berenices"); if (code == "CrA") return QString("Coronae Austrinae"); if (code == "CrB") return QString("Coronae Borealis"); if (code == "Crv") return QString("Corvi"); if (code == "Crt") return QString("Crateris"); if (code == "Cru") return QString("Crucis"); if (code == "Cyg") return QString("Cygni"); if (code == "Del") return QString("Delphini"); if (code == "Dor") return QString("Doradus"); if (code == "Dra") return QString("Draconis"); if (code == "Equ") return QString("Equulei"); if (code == "Eri") return QString("Eridani"); if (code == "For") return QString("Fornacis"); if (code == "Gem") return QString("Geminorum"); if (code == "Gru") return QString("Gruis"); if (code == "Her") return QString("Herculis"); if (code == "Hor") return QString("Horologii"); if (code == "Hya") return QString("Hydrae"); if (code == "Hyi") return QString("Hydri"); if (code == "Ind") return QString("Indi"); if (code == "Lac") return QString("Lacertae"); if (code == "Leo") return QString("Leonis"); if (code == "LMi") return QString("Leonis Minoris"); if (code == "Lep") return QString("Leporis"); if (code == "Lib") return QString("Librae"); if (code == "Lup") return QString("Lupi"); if (code == "Lyn") return QString("Lyncis"); if (code == "Lyr") return QString("Lyrae"); if (code == "Men") return QString("Mensae"); if (code == "Mic") return QString("Microscopii"); if (code == "Mon") return QString("Monocerotis"); if (code == "Mus") return QString("Muscae"); if (code == "Nor") return QString("Normae"); if (code == "Oct") return QString("Octantis"); if (code == "Oph") return QString("Ophiuchi"); if (code == "Ori") return QString("Orionis"); if (code == "Pav") return QString("Pavonis"); if (code == "Peg") return QString("Pegasi"); if (code == "Per") return QString("Persei"); if (code == "Phe") return QString("Phoenicis"); if (code == "Pic") return QString("Pictoris"); if (code == "Psc") return QString("Piscium"); if (code == "PsA") return QString("Piscis Austrini"); if (code == "Pup") return QString("Puppis"); if (code == "Pyx") return QString("Pyxidis"); if (code == "Ret") return QString("Reticuli"); if (code == "Sge") return QString("Sagittae"); if (code == "Sgr") return QString("Sagittarii"); if (code == "Sco") return QString("Scorpii"); if (code == "Scl") return QString("Sculptoris"); if (code == "Sct") return QString("Scuti"); if (code == "Ser") return QString("Serpentis"); if (code == "Sex") return QString("Sextantis"); if (code == "Tau") return QString("Tauri"); if (code == "Tel") return QString("Telescopii"); if (code == "Tri") return QString("Trianguli"); if (code == "TrA") return QString("Trianguli Australis"); if (code == "Tuc") return QString("Tucanae"); if (code == "UMa") return QString("Ursae Majoris"); if (code == "UMi") return QString("Ursae Minoris"); if (code == "Vel") return QString("Velorum"); if (code == "Vir") return QString("Virginis"); if (code == "Vol") return QString("Volantis"); if (code == "Vul") return QString("Vulpeculae"); return code; } QString constNameFromAbbrev(const QString &code) { if (code == "And") return QString("Andromeda"); if (code == "Ant") return QString("Antlia"); if (code == "Aps") return QString("Apus"); if (code == "Aqr") return QString("Aquarius"); if (code == "Aql") return QString("Aquila"); if (code == "Ara") return QString("Ara"); if (code == "Ari") return QString("Aries"); if (code == "Aur") return QString("Auriga"); if (code == "Boo") return QString("Bootes"); if (code == "Cae") return QString("Caelum"); if (code == "Cam") return QString("Camelopardalis"); if (code == "Cnc") return QString("Cancer"); if (code == "CVn") return QString("Canes Venatici"); if (code == "CMa") return QString("Canis Major"); if (code == "CMi") return QString("Canis Minor"); if (code == "Cap") return QString("Capricornus"); if (code == "Car") return QString("Carina"); if (code == "Cas") return QString("Cassiopeia"); if (code == "Cen") return QString("Centaurus"); if (code == "Cep") return QString("Cepheus"); if (code == "Cet") return QString("Cetus"); if (code == "Cha") return QString("Chamaeleon"); if (code == "Cir") return QString("Circinus"); if (code == "Col") return QString("Columba"); if (code == "Com") return QString("Coma Berenices"); if (code == "CrA") return QString("Corona Australis"); if (code == "CrB") return QString("Corona Borealis"); if (code == "Crv") return QString("Corvus"); if (code == "Crt") return QString("Crater"); if (code == "Cru") return QString("Crux"); if (code == "Cyg") return QString("Cygnus"); if (code == "Del") return QString("Delphinus"); if (code == "Dor") return QString("Doradus"); if (code == "Dra") return QString("Draco"); if (code == "Equ") return QString("Equuleus"); if (code == "Eri") return QString("Eridanus"); if (code == "For") return QString("Fornax"); if (code == "Gem") return QString("Gemini"); if (code == "Gru") return QString("Grus"); if (code == "Her") return QString("Hercules"); if (code == "Hor") return QString("Horologium"); if (code == "Hya") return QString("Hydra"); if (code == "Hyi") return QString("Hydrus"); if (code == "Ind") return QString("Indus"); if (code == "Lac") return QString("Lacerta"); if (code == "Leo") return QString("Leo"); if (code == "LMi") return QString("Leo Minor"); if (code == "Lep") return QString("Lepus"); if (code == "Lib") return QString("Libra"); if (code == "Lup") return QString("Lupus"); if (code == "Lyn") return QString("Lynx"); if (code == "Lyr") return QString("Lyra"); if (code == "Men") return QString("Mensa"); if (code == "Mic") return QString("Microscopium"); if (code == "Mon") return QString("Monoceros"); if (code == "Mus") return QString("Musca"); if (code == "Nor") return QString("Norma"); if (code == "Oct") return QString("Octans"); if (code == "Oph") return QString("Ophiuchus"); if (code == "Ori") return QString("Orion"); if (code == "Pav") return QString("Pavo"); if (code == "Peg") return QString("Pegasus"); if (code == "Per") return QString("Perseus"); if (code == "Phe") return QString("Phoenix"); if (code == "Pic") return QString("Pictor"); if (code == "Psc") return QString("Pisces"); if (code == "PsA") return QString("Piscis Austrinus"); if (code == "Pup") return QString("Puppis"); if (code == "Pyx") return QString("Pyxis"); if (code == "Ret") return QString("Reticulum"); if (code == "Sge") return QString("Sagitta"); if (code == "Sgr") return QString("Sagittarius"); if (code == "Sco") return QString("Scorpius"); if (code == "Scl") return QString("Sculptor"); if (code == "Sct") return QString("Scutum"); if (code == "Ser") return QString("Serpens"); if (code == "Sex") return QString("Sextans"); if (code == "Tau") return QString("Taurus"); if (code == "Tel") return QString("Telescopium"); if (code == "Tri") return QString("Triangulum"); if (code == "TrA") return QString("Triangulum Australe"); if (code == "Tuc") return QString("Tucana"); if (code == "UMa") return QString("Ursa Major"); if (code == "UMi") return QString("Ursa Minor"); if (code == "Vel") return QString("Vela"); if (code == "Vir") return QString("Virgo"); if (code == "Vol") return QString("Volans"); if (code == "Vul") return QString("Vulpecula"); return code; } QString constNameToAbbrev(const QString &fullName_) { QString fullName = fullName_.toLower(); if (fullName == "andromeda") return QString("And"); if (fullName == "antlia") return QString("Ant"); if (fullName == "apus") return QString("Aps"); if (fullName == "aquarius") return QString("Aqr"); if (fullName == "aquila") return QString("Aql"); if (fullName == "ara") return QString("Ara"); if (fullName == "aries") return QString("Ari"); if (fullName == "auriga") return QString("Aur"); if (fullName == "bootes") return QString("Boo"); if (fullName == "caelum") return QString("Cae"); if (fullName == "camelopardalis") return QString("Cam"); if (fullName == "cancer") return QString("Cnc"); if (fullName == "canes venatici") return QString("CVn"); if (fullName == "canis major") return QString("CMa"); if (fullName == "canis minor") return QString("CMi"); if (fullName == "capricornus") return QString("Cap"); if (fullName == "carina") return QString("Car"); if (fullName == "cassiopeia") return QString("Cas"); if (fullName == "centaurus") return QString("Cen"); if (fullName == "cepheus") return QString("Cep"); if (fullName == "cetus") return QString("Cet"); if (fullName == "chamaeleon") return QString("Cha"); if (fullName == "circinus") return QString("Cir"); if (fullName == "columba") return QString("Col"); if (fullName == "coma berenices") return QString("Com"); if (fullName == "corona australis") return QString("CrA"); if (fullName == "corona borealis") return QString("CrB"); if (fullName == "corvus") return QString("Crv"); if (fullName == "crater") return QString("Crt"); if (fullName == "crux") return QString("Cru"); if (fullName == "cygnus") return QString("Cyg"); if (fullName == "delphinus") return QString("Del"); if (fullName == "doradus") return QString("Dor"); if (fullName == "draco") return QString("Dra"); if (fullName == "equuleus") return QString("Equ"); if (fullName == "eridanus") return QString("Eri"); if (fullName == "fornax") return QString("For"); if (fullName == "gemini") return QString("Gem"); if (fullName == "grus") return QString("Gru"); if (fullName == "hercules") return QString("Her"); if (fullName == "horologium") return QString("Hor"); if (fullName == "hydra") return QString("Hya"); if (fullName == "hydrus") return QString("Hyi"); if (fullName == "indus") return QString("Ind"); if (fullName == "lacerta") return QString("Lac"); if (fullName == "leo") return QString("Leo"); if (fullName == "leo minor") return QString("LMi"); if (fullName == "lepus") return QString("Lep"); if (fullName == "libra") return QString("Lib"); if (fullName == "lupus") return QString("Lup"); if (fullName == "lynx") return QString("Lyn"); if (fullName == "lyra") return QString("Lyr"); if (fullName == "mensa") return QString("Men"); if (fullName == "microscopium") return QString("Mic"); if (fullName == "monoceros") return QString("Mon"); if (fullName == "musca") return QString("Mus"); if (fullName == "norma") return QString("Nor"); if (fullName == "octans") return QString("Oct"); if (fullName == "ophiuchus") return QString("Oph"); if (fullName == "orion") return QString("Ori"); if (fullName == "pavo") return QString("Pav"); if (fullName == "pegasus") return QString("Peg"); if (fullName == "perseus") return QString("Per"); if (fullName == "phoenix") return QString("Phe"); if (fullName == "pictor") return QString("Pic"); if (fullName == "pisces") return QString("Psc"); if (fullName == "piscis austrinus") return QString("PsA"); if (fullName == "puppis") return QString("Pup"); if (fullName == "pyxis") return QString("Pyx"); if (fullName == "reticulum") return QString("Ret"); if (fullName == "sagitta") return QString("Sge"); if (fullName == "sagittarius") return QString("Sgr"); if (fullName == "scorpius") return QString("Sco"); if (fullName == "sculptor") return QString("Scl"); if (fullName == "scutum") return QString("Sct"); if (fullName == "serpens") return QString("Ser"); if (fullName == "sextans") return QString("Sex"); if (fullName == "taurus") return QString("Tau"); if (fullName == "telescopium") return QString("Tel"); if (fullName == "triangulum") return QString("Tri"); if (fullName == "triangulum australe") return QString("TrA"); if (fullName == "tucana") return QString("Tuc"); if (fullName == "ursa major") return QString("UMa"); if (fullName == "ursa minor") return QString("UMi"); if (fullName == "vela") return QString("Vel"); if (fullName == "virgo") return QString("Vir"); if (fullName == "volans") return QString("Vol"); if (fullName == "vulpecula") return QString("Vul"); return fullName_; } QString constGenetiveToAbbrev(const QString &genetive_) { QString genetive = genetive_.toLower(); if (genetive == "andromedae") return QString("And"); if (genetive == "antliae") return QString("Ant"); if (genetive == "apodis") return QString("Aps"); if (genetive == "aquarii") return QString("Aqr"); if (genetive == "aquilae") return QString("Aql"); if (genetive == "arae") return QString("Ara"); if (genetive == "arietis") return QString("Ari"); if (genetive == "aurigae") return QString("Aur"); if (genetive == "bootis") return QString("Boo"); if (genetive == "caeli") return QString("Cae"); if (genetive == "camelopardalis") return QString("Cam"); if (genetive == "cancri") return QString("Cnc"); if (genetive == "canum venaticorum") return QString("CVn"); if (genetive == "canis majoris") return QString("CMa"); if (genetive == "canis minoris") return QString("CMi"); if (genetive == "capricorni") return QString("Cap"); if (genetive == "carinae") return QString("Car"); if (genetive == "cassiopeiae") return QString("Cas"); if (genetive == "centauri") return QString("Cen"); if (genetive == "cephei") return QString("Cep"); if (genetive == "ceti") return QString("Cet"); if (genetive == "chamaeleontis") return QString("Cha"); if (genetive == "circini") return QString("Cir"); if (genetive == "columbae") return QString("Col"); if (genetive == "comae berenices") return QString("Com"); if (genetive == "coronae austrinae") return QString("CrA"); if (genetive == "coronae borealis") return QString("CrB"); if (genetive == "corvi") return QString("Crv"); if (genetive == "crateris") return QString("Crt"); if (genetive == "crucis") return QString("Cru"); if (genetive == "cygni") return QString("Cyg"); if (genetive == "delphini") return QString("Del"); if (genetive == "doradus") return QString("Dor"); if (genetive == "draconis") return QString("Dra"); if (genetive == "equulei") return QString("Equ"); if (genetive == "eridani") return QString("Eri"); if (genetive == "fornacis") return QString("For"); if (genetive == "geminorum") return QString("Gem"); if (genetive == "gruis") return QString("Gru"); if (genetive == "herculis") return QString("Her"); if (genetive == "horologii") return QString("Hor"); if (genetive == "hydrae") return QString("Hya"); if (genetive == "hydri") return QString("Hyi"); if (genetive == "indi") return QString("Ind"); if (genetive == "lacertae") return QString("Lac"); if (genetive == "leonis") return QString("Leo"); if (genetive == "leonis minoris") return QString("LMi"); if (genetive == "leporis") return QString("Lep"); if (genetive == "librae") return QString("Lib"); if (genetive == "lupi") return QString("Lup"); if (genetive == "lyncis") return QString("Lyn"); if (genetive == "lyrae") return QString("Lyr"); if (genetive == "mensae") return QString("Men"); if (genetive == "microscopii") return QString("Mic"); if (genetive == "monocerotis") return QString("Mon"); if (genetive == "muscae") return QString("Mus"); if (genetive == "normae") return QString("Nor"); if (genetive == "octantis") return QString("Oct"); if (genetive == "ophiuchi") return QString("Oph"); if (genetive == "orionis") return QString("Ori"); if (genetive == "pavonis") return QString("Pav"); if (genetive == "pegasi") return QString("Peg"); if (genetive == "persei") return QString("Per"); if (genetive == "phoenicis") return QString("Phe"); if (genetive == "pictoris") return QString("Pic"); if (genetive == "piscium") return QString("Psc"); if (genetive == "piscis austrini") return QString("PsA"); if (genetive == "puppis") return QString("Pup"); if (genetive == "pyxidis") return QString("Pyx"); if (genetive == "reticuli") return QString("Ret"); if (genetive == "sagittae") return QString("Sge"); if (genetive == "sagittarii") return QString("Sgr"); if (genetive == "scorpii") return QString("Sco"); if (genetive == "sculptoris") return QString("Scl"); if (genetive == "scuti") return QString("Sct"); if (genetive == "serpentis") return QString("Ser"); if (genetive == "sextantis") return QString("Sex"); if (genetive == "tauri") return QString("Tau"); if (genetive == "telescopii") return QString("Tel"); if (genetive == "trianguli") return QString("Tri"); if (genetive == "trianguli australis") return QString("TrA"); if (genetive == "tucanae") return QString("Tuc"); if (genetive == "ursae majoris") return QString("UMa"); if (genetive == "ursae minoris") return QString("UMi"); if (genetive == "velorum") return QString("Vel"); if (genetive == "virginis") return QString("Vir"); if (genetive == "volantis") return QString("Vol"); if (genetive == "vulpeculae") return QString("Vul"); return genetive_; } QString Logging::_filename; void Logging::UseFile() { if (_filename.isEmpty()) { QDir dir; QString path = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "logs/" + QDateTime::currentDateTime().toString("yyyy-MM-dd"); dir.mkpath(path); QString name = "log_" + QDateTime::currentDateTime().toString("HH-mm-ss") + ".txt"; _filename = path + QStringLiteral("/") + name; // Clear file contents QFile file(_filename); file.open(QFile::WriteOnly); file.close(); } qSetMessagePattern("[%{time yyyy-MM-dd h:mm:ss.zzz t} %{if-debug}DEBG%{endif}%{if-info}INFO%{endif}%{if-warning}WARN%{endif}%{if-critical}CRIT%{endif}%{if-fatal}FATL%{endif}] %{if-category}[%{category}]%{endif} - %{message}"); qInstallMessageHandler(File); } void Logging::File(QtMsgType type, const QMessageLogContext &context, const QString &msg) { QFile file(_filename); if (file.open(QFile::Append | QIODevice::Text)) { QTextStream stream(&file); Write(stream, type, context, msg); } } void Logging::UseStdout() { qSetMessagePattern("[%{time yyyy-MM-dd h:mm:ss.zzz t} %{if-debug}DEBG%{endif}%{if-info}INFO%{endif}%{if-warning}WARN%{endif}%{if-critical}CRIT%{endif}%{if-fatal}FATL%{endif}] %{if-category}[%{category}]%{endif} - %{message}"); qInstallMessageHandler(Stdout); } void Logging::Stdout(QtMsgType type, const QMessageLogContext &context, const QString &msg) { QTextStream stream(stdout, QIODevice::WriteOnly); Write(stream, type, context, msg); } void Logging::UseStderr() { qInstallMessageHandler(Stderr); } void Logging::Stderr(QtMsgType type, const QMessageLogContext &context, const QString &msg) { QTextStream stream(stderr, QIODevice::WriteOnly); Write(stream, type,context, msg); } void Logging::Write(QTextStream &stream, QtMsgType type, const QMessageLogContext &context, const QString &msg) { stream << QDateTime::currentDateTime().toString("[yyyy-MM-ddThh:mm:ss.zzz t "); switch (type) { case QtInfoMsg: stream << "INFO ]"; break; case QtDebugMsg: stream << "DEBG ]"; break; case QtWarningMsg: stream << "WARN ]"; break; case QtCriticalMsg: stream << "CRIT ]"; break; case QtFatalMsg: stream << "FATL ]"; break; default: stream << "UNKN ]"; } stream << "[" << qSetFieldWidth(30) << context.category << qSetFieldWidth(0) << "] - "; stream << msg << endl; //stream << qFormatLogMessage(type, context, msg) << endl; } void Logging::UseDefault() { - qInstallMessageHandler(0); + qInstallMessageHandler(nullptr); } void Logging::Disable() { qInstallMessageHandler(Disabled); } void Logging::Disabled(QtMsgType, const QMessageLogContext &, const QString &) { } void Logging::SyncFilterRules() { QString rules = QString("org.kde.kstars.ekos.debug=%1\n" "org.kde.kstars.indi.debug=%2\n" "org.kde.kstars.ekos.mount.debug=%3\n" "org.kde.kstars.ekos.capture.debug=%4\n" "org.kde.kstars.ekos.focus.debug=%5\n" "org.kde.kstars.ekos.guide.debug=%6\n" "org.kde.kstars.ekos.align.debug=%7\n" "org.kde.kstars.ekos.mount.debug=%8\n" "org.kde.kstars.ekos.scheduler.debug=%9\n" "org.kde.kstars.debug=%1").arg( Options::verboseLogging() ? "true" : "false", Options::iNDILogging() ? "true" : "false", Options::fITSLogging() ? "true" : "false", Options::captureLogging() ? "true" : "false", Options::focusLogging() ? "true" : "false", Options::guideLogging() ? "true" : "false", Options::alignmentLogging() ? "true" : "false", Options::mountLogging() ? "true" : "false", Options::schedulerLogging() ? "true" : "false" ); QLoggingCategory::setFilterRules(rules); } /** This method provides a centralized location for the default paths to important external files used in the Options on different operating systems. Note that on OS X, if the user builds the app without indi, astrometry, and xplanet internally then the options below will be used. If the user drags the app from a dmg and has to install the KStars data directory, then most of these paths will be overwritten since it is preferrred to use the internal versions. **/ QString getDefaultPath(QString option) { QString snap = QProcessEnvironment::systemEnvironment().value("SNAP"); if (option == "fitsDir") { return QDir::homePath(); } else if (option == "indiServer") { #ifdef Q_OS_OSX return "/usr/local/bin/indiserver"; #endif return snap + "/usr/bin/indiserver"; } else if (option == "indiDriversDir") { #ifdef Q_OS_OSX return "/usr/local/share/indi"; #elif defined(Q_OS_LINUX) return snap + "/usr/share/indi"; #else return QStandardPaths::locate(QStandardPaths::GenericDataLocation, "indi", QStandardPaths::LocateDirectory); #endif } else if (option == "AstrometrySolverBinary") { #ifdef Q_OS_OSX return "/usr/local/bin/solve-field"; #endif return snap + "/usr/bin/solve-field"; } else if (option == "AstrometryWCSInfo") { #ifdef Q_OS_OSX return "/usr/local/bin/wcsinfo"; #endif return snap + "/usr/bin/wcsinfo"; } else if (option == "AstrometryConfFile") { #ifdef Q_OS_OSX return "/usr/local/etc/astrometry.cfg"; #endif return snap + "/etc/astrometry.cfg"; } else if (option == "XplanetPath") { #ifdef Q_OS_OSX return "/usr/local/bin/xplanet"; #endif return snap + "/usr/bin/xplanet"; } return QString(); } #ifdef Q_OS_OSX bool copyDataFolderFromAppBundleIfNeeded() //The method returns true if the data directory is good to go. { QString dataLocation = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kstars", QStandardPaths::LocateDirectory); if (dataLocation.isEmpty()) //If there is no kstars user data directory { QPointer wizard = new KSWizard(new QFrame()); wizard->exec(); //This will pause the startup until the user installs the data directory from the Wizard. dataLocation = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kstars", QStandardPaths::LocateDirectory); if (dataLocation.isEmpty()) return false; //This sets some important OS X options. Options::setIndiServerIsInternal(true); Options::setIndiServer("*Internal INDI Server*"); Options::setIndiDriversAreInternal(true); Options::setIndiDriversDir("*Internal INDI Drivers*"); Options::setAstrometrySolverIsInternal(true); Options::setAstrometrySolverBinary("*Internal Solver*"); Options::setAstrometryConfFileIsInternal(true); Options::setAstrometryConfFile("*Internal astrometry.cfg*"); Options::setAstrometryWCSIsInternal(true); Options::setAstrometryWCSInfo("*Internal wcsinfo*"); Options::setAstrometryUseNoFITS2FITS(false); Options::setXplanetIsInternal(true); Options::setXplanetPath("*Internal XPlanet*"); Options::setRunStartupWizard(false); //don't run on startup because we are doing it now. return true; //This means the data directory is good to go now that we created it from the default. } return true; //This means the data directory was good to go from the start and the wizard did not run. } void configureDefaultAstrometry() { QDir writableDir; QString astrometryPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/Astrometry/"; if (!astrometryPath.isEmpty()) { writableDir.mkdir(astrometryPath); astrometryPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "Astrometry", QStandardPaths::LocateDirectory); if (astrometryPath.isEmpty()) KMessageBox::sorry( 0, i18n("Error! The Astrometry Index File Directory does not exist and was not able to be created.")); else { QString confPath = QCoreApplication::applicationDirPath() + "/astrometry/bin/astrometry.cfg"; QFile confFile(confPath); QString contents; if (confFile.open(QIODevice::ReadOnly) == false) KMessageBox::error(0, i18n("Internal Astrometry Configuration File Read Error.")); else { QTextStream in(&confFile); QString line; bool foundPathBefore = false; bool fileNeedsUpdating = false; while (!in.atEnd()) { line = in.readLine(); if (line.trimmed().startsWith(QLatin1String("add_path"))) { if (!foundPathBefore) //This will ensure there is not more than one add_path line in the file. { foundPathBefore = true; QString dataDir = line.trimmed().mid(9).trimmed(); if (dataDir != astrometryPath) //Update to the correct path. { contents += "add_path " + astrometryPath + '\n'; fileNeedsUpdating = true; } } } else { contents += line + '\n'; } } confFile.close(); if (fileNeedsUpdating) { if (confFile.open(QIODevice::WriteOnly) == false) KMessageBox::error(0, i18n("Internal Astrometry Configuration File Write Error.")); else { QTextStream out(&confFile); out << contents; confFile.close(); } } } } } } bool copyRecursively(QString sourceFolder, QString destFolder) { bool success = false; QDir sourceDir(sourceFolder); if (!sourceDir.exists()) return false; QDir destDir(destFolder); if (!destDir.exists()) destDir.mkdir(destFolder); QStringList files = sourceDir.entryList(QDir::Files); for (int i = 0; i < files.count(); i++) { QString srcName = sourceFolder + QDir::separator() + files[i]; QString destName = destFolder + QDir::separator() + files[i]; success = QFile::copy(srcName, destName); if (!success) return false; } files.clear(); files = sourceDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); for (int i = 0; i < files.count(); i++) { QString srcName = sourceFolder + QDir::separator() + files[i]; QString destName = destFolder + QDir::separator() + files[i]; success = copyRecursively(srcName, destName); if (!success) return false; } return true; } #endif QByteArray getJPLQueryString(const QByteArray &kind, const QByteArray &dataFields, const QVector &filters) { QByteArray query("obj_group=all&obj_kind=" + kind + "&obj_numbered=num&OBJ_field=0&ORB_field=0"); // Apply filters: for (int i = 0; i < filters.length(); i++) { QString f = QString::number(i + 1); query += "&c" + f + "_group=OBJ&c1_item=" + filters[i].item + "&c" + f + "_op=" + filters[i].op + "&c" + f + "_value=" + filters[i].value; } // Apply query data fields... query += "&c_fields=" + dataFields; query += "&table_format=CSV&max_rows=10&format_option=full&query=Generate%20Table&." "cgifields=format_option&.cgifields=field_list&.cgifields=obj_kind&.cgifie" "lds=obj_group&.cgifields=obj_numbered&.cgifields=combine_mode&.cgifields=" "ast_orbit_class&.cgifields=table_format&.cgifields=ORB_field_set&.cgifiel" "ds=OBJ_field_set&.cgifields=preset_field_set&.cgifields=com_orbit_class"; return query; } } diff --git a/kstars/auxiliary/qcustomplot.cpp b/kstars/auxiliary/qcustomplot.cpp index c8183e7e9..6faa812ee 100644 --- a/kstars/auxiliary/qcustomplot.cpp +++ b/kstars/auxiliary/qcustomplot.cpp @@ -1,31213 +1,31213 @@ /*************************************************************************** ** ** ** QCustomPlot, an easy to use, modern plotting widget for Qt ** ** Copyright (C) 2011-2016 Emanuel Eichhammer ** ** ** ** 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 3 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, see http://www.gnu.org/licenses/. ** ** ** **************************************************************************** ** Author: Emanuel Eichhammer ** ** Website/Contact: http://www.qcustomplot.com/ ** ** Date: 13.09.16 ** ** Version: 2.0.0-beta ** ****************************************************************************/ #include "qcustomplot.h" /* including file 'src/vector2d.cpp', size 7340 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPVector2D //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPVector2D \brief Represents two doubles as a mathematical 2D vector This class acts as a replacement for QVector2D with the advantage of double precision instead of single, and some convenience methods tailored for the QCustomPlot library. */ /* start documentation of inline functions */ /*! \fn void QCPVector2D::setX(double x) Sets the x coordinate of this vector to \a x. \see setY */ /*! \fn void QCPVector2D::setY(double y) Sets the y coordinate of this vector to \a y. \see setX */ /*! \fn double QCPVector2D::length() const Returns the length of this vector. \see lengthSquared */ /*! \fn double QCPVector2D::lengthSquared() const Returns the squared length of this vector. In some situations, e.g. when just trying to find the shortest vector of a group, this is faster than calculating \ref length, because it avoids calculation of a square root. \see length */ /*! \fn QPoint QCPVector2D::toPoint() const Returns a QPoint which has the x and y coordinates of this vector, truncating any floating point information. \see toPointF */ /*! \fn QPointF QCPVector2D::toPointF() const Returns a QPointF which has the x and y coordinates of this vector. \see toPoint */ /*! \fn bool QCPVector2D::isNull() const Returns whether this vector is null. A vector is null if \c qIsNull returns true for both x and y coordinates, i.e. if both are binary equal to 0. */ /*! \fn QCPVector2D QCPVector2D::perpendicular() const Returns a vector perpendicular to this vector, with the same length. */ /*! \fn double QCPVector2D::dot() const Returns the dot/scalar product of this vector with the specified vector \a vec. */ /* end documentation of inline functions */ /*! Creates a QCPVector2D object and initializes the x and y coordinates to 0. */ QCPVector2D::QCPVector2D() : mX(0), mY(0) { } /*! Creates a QCPVector2D object and initializes the \a x and \a y coordinates with the specified values. */ QCPVector2D::QCPVector2D(double x, double y) : mX(x), mY(y) { } /*! Creates a QCPVector2D object and initializes the x and y coordinates respective coordinates of the specified \a point. */ QCPVector2D::QCPVector2D(const QPoint &point) : mX(point.x()), mY(point.y()) { } /*! Creates a QCPVector2D object and initializes the x and y coordinates respective coordinates of the specified \a point. */ QCPVector2D::QCPVector2D(const QPointF &point) : mX(point.x()), mY(point.y()) { } /*! Normalizes this vector. After this operation, the length of the vector is equal to 1. \see normalized, length, lengthSquared */ void QCPVector2D::normalize() { double len = length(); mX /= len; mY /= len; } /*! Returns a normalized version of this vector. The length of the returned vector is equal to 1. \see normalize, length, lengthSquared */ QCPVector2D QCPVector2D::normalized() const { QCPVector2D result(mX, mY); result.normalize(); return result; } /*! \overload Returns the squared shortest distance of this vector (interpreted as a point) to the finite line segment given by \a start and \a end. \see distanceToStraightLine */ double QCPVector2D::distanceSquaredToLine(const QCPVector2D &start, const QCPVector2D &end) const { QCPVector2D v(end - start); double vLengthSqr = v.lengthSquared(); if (!qFuzzyIsNull(vLengthSqr)) { double mu = v.dot(*this - start) / vLengthSqr; if (mu < 0) return (*this - start).lengthSquared(); else if (mu > 1) return (*this - end).lengthSquared(); else return ((start + mu * v) - *this).lengthSquared(); } else return (*this - start).lengthSquared(); } /*! \overload Returns the squared shortest distance of this vector (interpreted as a point) to the finite line segment given by \a line. \see distanceToStraightLine */ double QCPVector2D::distanceSquaredToLine(const QLineF &line) const { return distanceSquaredToLine(QCPVector2D(line.p1()), QCPVector2D(line.p2())); } /*! Returns the shortest distance of this vector (interpreted as a point) to the infinite straight line given by a \a base point and a \a direction vector. \see distanceSquaredToLine */ double QCPVector2D::distanceToStraightLine(const QCPVector2D &base, const QCPVector2D &direction) const { return qAbs((*this - base).dot(direction.perpendicular())) / direction.length(); } /*! Scales this vector by the given \a factor, i.e. the x and y components are multiplied by \a factor. */ QCPVector2D &QCPVector2D::operator*=(double factor) { mX *= factor; mY *= factor; return *this; } /*! Scales this vector by the given \a divisor, i.e. the x and y components are divided by \a divisor. */ QCPVector2D &QCPVector2D::operator/=(double divisor) { mX /= divisor; mY /= divisor; return *this; } /*! Adds the given \a vector to this vector component-wise. */ QCPVector2D &QCPVector2D::operator+=(const QCPVector2D &vector) { mX += vector.mX; mY += vector.mY; return *this; } /*! subtracts the given \a vector from this vector component-wise. */ QCPVector2D &QCPVector2D::operator-=(const QCPVector2D &vector) { mX -= vector.mX; mY -= vector.mY; return *this; } /* end of 'src/vector2d.cpp' */ /* including file 'src/painter.cpp', size 8670 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPPainter //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPPainter \brief QPainter subclass used internally This QPainter subclass is used to provide some extended functionality e.g. for tweaking position consistency between antialiased and non-antialiased painting. Further it provides workarounds for QPainter quirks. \warning This class intentionally hides non-virtual functions of QPainter, e.g. setPen, save and restore. So while it is possible to pass a QCPPainter instance to a function that expects a QPainter pointer, some of the workarounds and tweaks will be unavailable to the function (because it will call the base class implementations of the functions actually hidden by QCPPainter). */ /*! Creates a new QCPPainter instance and sets default values */ QCPPainter::QCPPainter() : QPainter(), mModes(pmDefault), mIsAntialiasing(false) { // don't setRenderHint(QPainter::NonCosmeticDefautPen) here, because painter isn't active yet and // a call to begin() will follow } /*! Creates a new QCPPainter instance on the specified paint \a device and sets default values. Just like the analogous QPainter constructor, begins painting on \a device immediately. Like \ref begin, this method sets QPainter::NonCosmeticDefaultPen in Qt versions before Qt5. */ QCPPainter::QCPPainter(QPaintDevice *device) : QPainter(device), mModes(pmDefault), mIsAntialiasing(false) { #if QT_VERSION < \ QT_VERSION_CHECK( \ 5, 0, \ 0) // before Qt5, default pens used to be cosmetic if NonCosmeticDefaultPen flag isn't set. So we set it to get consistency across Qt versions. if (isActive()) setRenderHint(QPainter::NonCosmeticDefaultPen); #endif } /*! Sets the pen of the painter and applies certain fixes to it, depending on the mode of this QCPPainter. \note this function hides the non-virtual base class implementation. */ void QCPPainter::setPen(const QPen &pen) { QPainter::setPen(pen); if (mModes.testFlag(pmNonCosmetic)) makeNonCosmetic(); } /*! \overload Sets the pen (by color) of the painter and applies certain fixes to it, depending on the mode of this QCPPainter. \note this function hides the non-virtual base class implementation. */ void QCPPainter::setPen(const QColor &color) { QPainter::setPen(color); if (mModes.testFlag(pmNonCosmetic)) makeNonCosmetic(); } /*! \overload Sets the pen (by style) of the painter and applies certain fixes to it, depending on the mode of this QCPPainter. \note this function hides the non-virtual base class implementation. */ void QCPPainter::setPen(Qt::PenStyle penStyle) { QPainter::setPen(penStyle); if (mModes.testFlag(pmNonCosmetic)) makeNonCosmetic(); } /*! \overload Works around a Qt bug introduced with Qt 4.8 which makes drawing QLineF unpredictable when antialiasing is disabled. Thus when antialiasing is disabled, it rounds the \a line to integer coordinates and then passes it to the original drawLine. \note this function hides the non-virtual base class implementation. */ void QCPPainter::drawLine(const QLineF &line) { if (mIsAntialiasing || mModes.testFlag(pmVectorized)) QPainter::drawLine(line); else QPainter::drawLine(line.toLine()); } /*! Sets whether painting uses antialiasing or not. Use this method instead of using setRenderHint with QPainter::Antialiasing directly, as it allows QCPPainter to regain pixel exactness between antialiased and non-antialiased painting (Since Qt < 5.0 uses slightly different coordinate systems for AA/Non-AA painting). */ void QCPPainter::setAntialiasing(bool enabled) { setRenderHint(QPainter::Antialiasing, enabled); if (mIsAntialiasing != enabled) { mIsAntialiasing = enabled; if (!mModes.testFlag(pmVectorized)) // antialiasing half-pixel shift only needed for rasterized outputs { if (mIsAntialiasing) translate(0.5, 0.5); else translate(-0.5, -0.5); } } } /*! Sets the mode of the painter. This controls whether the painter shall adjust its fixes/workarounds optimized for certain output devices. */ void QCPPainter::setModes(QCPPainter::PainterModes modes) { mModes = modes; } /*! Sets the QPainter::NonCosmeticDefaultPen in Qt versions before Qt5 after beginning painting on \a device. This is necessary to get cosmetic pen consistency across Qt versions, because since Qt5, all pens are non-cosmetic by default, and in Qt4 this render hint must be set to get that behaviour. The Constructor \ref QCPPainter(QPaintDevice *device) which directly starts painting also sets the render hint as appropriate. \note this function hides the non-virtual base class implementation. */ bool QCPPainter::begin(QPaintDevice *device) { bool result = QPainter::begin(device); #if QT_VERSION < \ QT_VERSION_CHECK( \ 5, 0, \ 0) // before Qt5, default pens used to be cosmetic if NonCosmeticDefaultPen flag isn't set. So we set it to get consistency across Qt versions. if (result) setRenderHint(QPainter::NonCosmeticDefaultPen); #endif return result; } /*! \overload Sets the mode of the painter. This controls whether the painter shall adjust its fixes/workarounds optimized for certain output devices. */ void QCPPainter::setMode(QCPPainter::PainterMode mode, bool enabled) { if (!enabled && mModes.testFlag(mode)) mModes &= ~mode; else if (enabled && !mModes.testFlag(mode)) mModes |= mode; } /*! Saves the painter (see QPainter::save). Since QCPPainter adds some new internal state to QPainter, the save/restore functions are reimplemented to also save/restore those members. \note this function hides the non-virtual base class implementation. \see restore */ void QCPPainter::save() { mAntialiasingStack.push(mIsAntialiasing); QPainter::save(); } /*! Restores the painter (see QPainter::restore). Since QCPPainter adds some new internal state to QPainter, the save/restore functions are reimplemented to also save/restore those members. \note this function hides the non-virtual base class implementation. \see save */ void QCPPainter::restore() { if (!mAntialiasingStack.isEmpty()) mIsAntialiasing = mAntialiasingStack.pop(); else qDebug() << Q_FUNC_INFO << "Unbalanced save/restore"; QPainter::restore(); } /*! Changes the pen width to 1 if it currently is 0. This function is called in the \ref setPen overrides when the \ref pmNonCosmetic mode is set. */ void QCPPainter::makeNonCosmetic() { if (qFuzzyIsNull(pen().widthF())) { QPen p = pen(); p.setWidth(1); QPainter::setPen(p); } } /* end of 'src/painter.cpp' */ /* including file 'src/paintbuffer.cpp', size 18502 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAbstractPaintBuffer //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPAbstractPaintBuffer \brief The abstract base class for paint buffers, which define the rendering backend This abstract base class defines the basic interface that a paint buffer needs to provide in order to be usable by QCustomPlot. A paint buffer manages both a surface to draw onto, and the matching paint device. The size of the surface can be changed via \ref setSize. External classes (\ref QCustomPlot and \ref QCPLayer) request a painter via \ref startPainting and then perform the draw calls. Once the painting is complete, \ref donePainting is called, so the paint buffer implementation can do clean up if necessary. Before rendering a frame, each paint buffer is usually filled with a color using \ref clear (usually the color is \c Qt::transparent), to remove the contents of the previous frame. The simplest paint buffer implementation is \ref QCPPaintBufferPixmap which allows regular software rendering via the raster engine. Hardware accelerated rendering via pixel buffers and frame buffer objects is provided by \ref QCPPaintBufferGlPbuffer and \ref QCPPaintBufferGlFbo. They are used automatically if \ref QCustomPlot::setOpenGl is enabled. */ /* start documentation of pure virtual functions */ /*! \fn virtual QCPPainter *QCPAbstractPaintBuffer::startPainting() = 0 Returns a \ref QCPPainter which is ready to draw to this buffer. The ownership and thus the responsibility to delete the painter after the painting operations are complete is given to the caller of this method. Once you are done using the painter, delete the painter and call \ref donePainting. While a painter generated with this method is active, you must not call \ref setSize, \ref setDevicePixelRatio or \ref clear. This method may return 0, if a painter couldn't be activated on the buffer. This usually indicates a problem with the respective painting backend. */ /*! \fn virtual void QCPAbstractPaintBuffer::draw(QCPPainter *painter) const = 0 Draws the contents of this buffer with the provided \a painter. This is the method that is used to finally join all paint buffers and draw them onto the screen. */ /*! \fn virtual void QCPAbstractPaintBuffer::clear(const QColor &color) = 0 Fills the entire buffer with the provided \a color. To have an empty transparent buffer, use the named color \c Qt::transparent. This method must not be called if there is currently a painter (acquired with \ref startPainting) active. */ /*! \fn virtual void QCPAbstractPaintBuffer::reallocateBuffer() = 0 Reallocates the internal buffer with the currently configured size (\ref setSize) and device pixel ratio, if applicable (\ref setDevicePixelRatio). It is called as soon as any of those properties are changed on this paint buffer. \note Subclasses of \ref QCPAbstractPaintBuffer must call their reimplementation of this method in their constructor, to perform the first allocation (this can not be done by the base class because calling pure virtual methods in base class constructors is not possible). */ /* end documentation of pure virtual functions */ /* start documentation of inline functions */ /*! \fn virtual void QCPAbstractPaintBuffer::donePainting() If you have acquired a \ref QCPPainter to paint onto this paint buffer via \ref startPainting, call this method as soon as you are done with the painting operations and have deleted the painter. paint buffer subclasses may use this method to perform any type of cleanup that is necessary. The default implementation does nothing. */ /* end documentation of inline functions */ /*! Creates a paint buffer and initializes it with the provided \a size and \a devicePixelRatio. Subclasses must call their \ref reallocateBuffer implementation in their respective constructors. */ QCPAbstractPaintBuffer::QCPAbstractPaintBuffer(const QSize &size, double devicePixelRatio) : mSize(size), mDevicePixelRatio(devicePixelRatio), mInvalidated(true) { } QCPAbstractPaintBuffer::~QCPAbstractPaintBuffer() { } /*! Sets the paint buffer size. The buffer is reallocated (by calling \ref reallocateBuffer), so any painters that were obtained by \ref startPainting are invalidated and must not be used after calling this method. If \a size is already the current buffer size, this method does nothing. */ void QCPAbstractPaintBuffer::setSize(const QSize &size) { if (mSize != size) { mSize = size; reallocateBuffer(); } } /*! Sets the invalidated flag to \a invalidated. This mechanism is used internally in conjunction with isolated replotting of \ref QCPLayer instances (in \ref QCPLayer::lmBuffered mode). If \ref QCPLayer::replot is called on a buffered layer, i.e. an isolated repaint of only that layer (and its dedicated paint buffer) is requested, QCustomPlot will decide depending on the invalidated flags of other paint buffers whether it also replots them, instead of only the layer on which the replot was called. The invalidated flag is set to true when \ref QCPLayer association has changed, i.e. if layers were added or removed from this buffer, or if they were reordered. It is set to false as soon as all associated \ref QCPLayer instances are drawn onto the buffer. Under normal circumstances, it is not necessary to manually call this method. */ void QCPAbstractPaintBuffer::setInvalidated(bool invalidated) { mInvalidated = invalidated; } /*! Sets the device pixel ratio to \a ratio. This is useful to render on high-DPI output devices. The ratio is automatically set to the device pixel ratio used by the parent QCustomPlot instance. The buffer is reallocated (by calling \ref reallocateBuffer), so any painters that were obtained by \ref startPainting are invalidated and must not be used after calling this method. \note This method is only available for Qt versions 5.4 and higher. */ void QCPAbstractPaintBuffer::setDevicePixelRatio(double ratio) { if (!qFuzzyCompare(ratio, mDevicePixelRatio)) { #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED mDevicePixelRatio = ratio; reallocateBuffer(); #else qDebug() << Q_FUNC_INFO << "Device pixel ratios not supported for Qt versions before 5.4"; mDevicePixelRatio = 1.0; #endif } } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPPaintBufferPixmap //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPPaintBufferPixmap \brief A paint buffer based on QPixmap, using software raster rendering This paint buffer is the default and fall-back paint buffer which uses software rendering and QPixmap as internal buffer. It is used if \ref QCustomPlot::setOpenGl is false. */ /*! Creates a pixmap paint buffer instancen with the specified \a size and \a devicePixelRatio, if applicable. */ QCPPaintBufferPixmap::QCPPaintBufferPixmap(const QSize &size, double devicePixelRatio) : QCPAbstractPaintBuffer(size, devicePixelRatio) { QCPPaintBufferPixmap::reallocateBuffer(); } QCPPaintBufferPixmap::~QCPPaintBufferPixmap() { } /* inherits documentation from base class */ QCPPainter *QCPPaintBufferPixmap::startPainting() { QCPPainter *result = new QCPPainter(&mBuffer); result->setRenderHint(QPainter::HighQualityAntialiasing); return result; } /* inherits documentation from base class */ void QCPPaintBufferPixmap::draw(QCPPainter *painter) const { if (painter && painter->isActive()) painter->drawPixmap(0, 0, mBuffer); else qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed"; } /* inherits documentation from base class */ void QCPPaintBufferPixmap::clear(const QColor &color) { mBuffer.fill(color); } /* inherits documentation from base class */ void QCPPaintBufferPixmap::reallocateBuffer() { setInvalidated(); if (!qFuzzyCompare(1.0, mDevicePixelRatio)) { #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED mBuffer = QPixmap(mSize * mDevicePixelRatio); mBuffer.setDevicePixelRatio(mDevicePixelRatio); #else qDebug() << Q_FUNC_INFO << "Device pixel ratios not supported for Qt versions before 5.4"; mDevicePixelRatio = 1.0; mBuffer = QPixmap(mSize); #endif } else { mBuffer = QPixmap(mSize); } } #ifdef QCP_OPENGL_PBUFFER //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPPaintBufferGlPbuffer //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPPaintBufferGlPbuffer \brief A paint buffer based on OpenGL pixel buffers, using hardware accelerated rendering This paint buffer is one of the OpenGL paint buffers which facilitate hardware accelerated plot rendering. It is based on OpenGL pixel buffers (pbuffer) and is used in Qt versions before 5.0. (See \ref QCPPaintBufferGlFbo used in newer Qt versions.) The OpenGL paint buffers are used if \ref QCustomPlot::setOpenGl is set to true, and if they are supported by the system. */ /*! Creates a \ref QCPPaintBufferGlPbuffer instance with the specified \a size and \a devicePixelRatio, if applicable. The parameter \a multisamples defines how many samples are used per pixel. Higher values thus result in higher quality antialiasing. If the specified \a multisamples value exceeds the capability of the graphics hardware, the highest supported multisampling is used. */ QCPPaintBufferGlPbuffer::QCPPaintBufferGlPbuffer(const QSize &size, double devicePixelRatio, int multisamples) : QCPAbstractPaintBuffer(size, devicePixelRatio), mGlPBuffer(0), mMultisamples(qMax(0, multisamples)) { QCPPaintBufferGlPbuffer::reallocateBuffer(); } QCPPaintBufferGlPbuffer::~QCPPaintBufferGlPbuffer() { if (mGlPBuffer) delete mGlPBuffer; } /* inherits documentation from base class */ QCPPainter *QCPPaintBufferGlPbuffer::startPainting() { if (!mGlPBuffer->isValid()) { qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?"; return 0; } QCPPainter *result = new QCPPainter(mGlPBuffer); result->setRenderHint(QPainter::HighQualityAntialiasing); return result; } /* inherits documentation from base class */ void QCPPaintBufferGlPbuffer::draw(QCPPainter *painter) const { if (!painter || !painter->isActive()) { qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed"; return; } if (!mGlPBuffer->isValid()) { qDebug() << Q_FUNC_INFO << "OpenGL pbuffer isn't valid, reallocateBuffer was not called?"; return; } painter->drawImage(0, 0, mGlPBuffer->toImage()); } /* inherits documentation from base class */ void QCPPaintBufferGlPbuffer::clear(const QColor &color) { if (mGlPBuffer->isValid()) { mGlPBuffer->makeCurrent(); glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); mGlPBuffer->doneCurrent(); } else qDebug() << Q_FUNC_INFO << "OpenGL pbuffer invalid or context not current"; } /* inherits documentation from base class */ void QCPPaintBufferGlPbuffer::reallocateBuffer() { if (mGlPBuffer) delete mGlPBuffer; QGLFormat format; format.setAlpha(true); format.setSamples(mMultisamples); mGlPBuffer = new QGLPixelBuffer(mSize, format); } #endif // QCP_OPENGL_PBUFFER #ifdef QCP_OPENGL_FBO //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPPaintBufferGlFbo //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPPaintBufferGlFbo \brief A paint buffer based on OpenGL frame buffers objects, using hardware accelerated rendering This paint buffer is one of the OpenGL paint buffers which facilitate hardware accelerated plot rendering. It is based on OpenGL frame buffer objects (fbo) and is used in Qt versions 5.0 and higher. (See \ref QCPPaintBufferGlPbuffer used in older Qt versions.) The OpenGL paint buffers are used if \ref QCustomPlot::setOpenGl is set to true, and if they are supported by the system. */ /*! Creates a \ref QCPPaintBufferGlFbo instance with the specified \a size and \a devicePixelRatio, if applicable. All frame buffer objects shall share one OpenGL context and paint device, which need to be set up externally and passed via \a glContext and \a glPaintDevice. The set-up is done in \ref QCustomPlot::setupOpenGl and the context and paint device are managed by the parent QCustomPlot instance. */ QCPPaintBufferGlFbo::QCPPaintBufferGlFbo(const QSize &size, double devicePixelRatio, QWeakPointer glContext, QWeakPointer glPaintDevice) : QCPAbstractPaintBuffer(size, devicePixelRatio), mGlContext(glContext), mGlPaintDevice(glPaintDevice), mGlFrameBuffer(0) { QCPPaintBufferGlFbo::reallocateBuffer(); } QCPPaintBufferGlFbo::~QCPPaintBufferGlFbo() { if (mGlFrameBuffer) delete mGlFrameBuffer; } /* inherits documentation from base class */ QCPPainter *QCPPaintBufferGlFbo::startPainting() { if (mGlPaintDevice.isNull()) { qDebug() << Q_FUNC_INFO << "OpenGL paint device doesn't exist"; return 0; } if (!mGlFrameBuffer) { qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?"; return 0; } if (QOpenGLContext::currentContext() != mGlContext.data()) mGlContext.data()->makeCurrent(mGlContext.data()->surface()); mGlFrameBuffer->bind(); QCPPainter *result = new QCPPainter(mGlPaintDevice.data()); result->setRenderHint(QPainter::HighQualityAntialiasing); return result; } /* inherits documentation from base class */ void QCPPaintBufferGlFbo::donePainting() { if (mGlFrameBuffer && mGlFrameBuffer->isBound()) mGlFrameBuffer->release(); else qDebug() << Q_FUNC_INFO << "Either OpenGL frame buffer not valid or was not bound"; } /* inherits documentation from base class */ void QCPPaintBufferGlFbo::draw(QCPPainter *painter) const { if (!painter || !painter->isActive()) { qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed"; return; } if (!mGlFrameBuffer) { qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?"; return; } painter->drawImage(0, 0, mGlFrameBuffer->toImage()); } /* inherits documentation from base class */ void QCPPaintBufferGlFbo::clear(const QColor &color) { if (mGlContext.isNull()) { qDebug() << Q_FUNC_INFO << "OpenGL context doesn't exist"; return; } if (!mGlFrameBuffer) { qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?"; return; } if (QOpenGLContext::currentContext() != mGlContext.data()) mGlContext.data()->makeCurrent(mGlContext.data()->surface()); mGlFrameBuffer->bind(); glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); mGlFrameBuffer->release(); } /* inherits documentation from base class */ void QCPPaintBufferGlFbo::reallocateBuffer() { // release and delete possibly existing framebuffer: if (mGlFrameBuffer) { if (mGlFrameBuffer->isBound()) mGlFrameBuffer->release(); delete mGlFrameBuffer; mGlFrameBuffer = 0; } if (mGlContext.isNull()) { qDebug() << Q_FUNC_INFO << "OpenGL context doesn't exist"; return; } if (mGlPaintDevice.isNull()) { qDebug() << Q_FUNC_INFO << "OpenGL paint device doesn't exist"; return; } // create new fbo with appropriate size: mGlContext.data()->makeCurrent(mGlContext.data()->surface()); QOpenGLFramebufferObjectFormat frameBufferFormat; frameBufferFormat.setSamples(mGlContext.data()->format().samples()); frameBufferFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); mGlFrameBuffer = new QOpenGLFramebufferObject(mSize * mDevicePixelRatio, frameBufferFormat); if (mGlPaintDevice.data()->size() != mSize * mDevicePixelRatio) mGlPaintDevice.data()->setSize(mSize * mDevicePixelRatio); #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED mGlPaintDevice.data()->setDevicePixelRatio(mDevicePixelRatio); #endif } #endif // QCP_OPENGL_FBO /* end of 'src/paintbuffer.cpp' */ /* including file 'src/layer.cpp', size 37064 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPLayer //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPLayer \brief A layer that may contain objects, to control the rendering order The Layering system of QCustomPlot is the mechanism to control the rendering order of the elements inside the plot. It is based on the two classes QCPLayer and QCPLayerable. QCustomPlot holds an ordered list of one or more instances of QCPLayer (see QCustomPlot::addLayer, QCustomPlot::layer, QCustomPlot::moveLayer, etc.). When replotting, QCustomPlot goes through the list of layers bottom to top and successively draws the layerables of the layers into the paint buffer(s). A QCPLayer contains an ordered list of QCPLayerable instances. QCPLayerable is an abstract base class from which almost all visible objects derive, like axes, grids, graphs, items, etc. \section qcplayer-defaultlayers Default layers Initially, QCustomPlot has six layers: "background", "grid", "main", "axes", "legend" and "overlay" (in that order). On top is the "overlay" layer, which only contains the QCustomPlot's selection rect (\ref QCustomPlot::selectionRect). The next two layers "axes" and "legend" contain the default axes and legend, so they will be drawn above plottables. In the middle, there is the "main" layer. It is initially empty and set as the current layer (see QCustomPlot::setCurrentLayer). This means, all new plottables, items etc. are created on this layer by default. Then comes the "grid" layer which contains the QCPGrid instances (which belong tightly to QCPAxis, see \ref QCPAxis::grid). The Axis rect background shall be drawn behind everything else, thus the default QCPAxisRect instance is placed on the "background" layer. Of course, the layer affiliation of the individual objects can be changed as required (\ref QCPLayerable::setLayer). \section qcplayer-ordering Controlling the rendering order via layers Controlling the ordering of layerables in the plot is easy: Create a new layer in the position you want the layerable to be in, e.g. above "main", with \ref QCustomPlot::addLayer. Then set the current layer with \ref QCustomPlot::setCurrentLayer to that new layer and finally create the objects normally. They will be placed on the new layer automatically, due to the current layer setting. Alternatively you could have also ignored the current layer setting and just moved the objects with \ref QCPLayerable::setLayer to the desired layer after creating them. It is also possible to move whole layers. For example, If you want the grid to be shown in front of all plottables/items on the "main" layer, just move it above "main" with QCustomPlot::moveLayer. The rendering order within one layer is simply by order of creation or insertion. The item created last (or added last to the layer), is drawn on top of all other objects on that layer. When a layer is deleted, the objects on it are not deleted with it, but fall on the layer below the deleted layer, see QCustomPlot::removeLayer. \section qcplayer-buffering Replotting only a specific layer If the layer mode (\ref setMode) is set to \ref lmBuffered, you can replot only this specific layer by calling \ref replot. In certain situations this can provide better replot performance, compared with a full replot of all layers. Upon creation of a new layer, the layer mode is initialized to \ref lmLogical. The only layer that is set to \ref lmBuffered in a new \ref QCustomPlot instance is the "overlay" layer, containing the selection rect. */ /* start documentation of inline functions */ /*! \fn QList QCPLayer::children() const Returns a list of all layerables on this layer. The order corresponds to the rendering order: layerables with higher indices are drawn above layerables with lower indices. */ /*! \fn int QCPLayer::index() const Returns the index this layer has in the QCustomPlot. The index is the integer number by which this layer can be accessed via \ref QCustomPlot::layer. Layers with higher indices will be drawn above layers with lower indices. */ /* end documentation of inline functions */ /*! Creates a new QCPLayer instance. Normally you shouldn't directly instantiate layers, use \ref QCustomPlot::addLayer instead. \warning It is not checked that \a layerName is actually a unique layer name in \a parentPlot. This check is only performed by \ref QCustomPlot::addLayer. */ QCPLayer::QCPLayer(QCustomPlot *parentPlot, const QString &layerName) : QObject(parentPlot), mParentPlot(parentPlot), mName(layerName), mIndex(-1), // will be set to a proper value by the QCustomPlot layer creation function mVisible(true), mMode(lmLogical) { // Note: no need to make sure layerName is unique, because layer // management is done with QCustomPlot functions. } QCPLayer::~QCPLayer() { // If child layerables are still on this layer, detach them, so they don't try to reach back to this // then invalid layer once they get deleted/moved themselves. This only happens when layers are deleted // directly, like in the QCustomPlot destructor. (The regular layer removal procedure for the user is to // call QCustomPlot::removeLayer, which moves all layerables off this layer before deleting it.) while (!mChildren.isEmpty()) - mChildren.last()->setLayer(0); // removes itself from mChildren via removeChild() + mChildren.last()->setLayer(nullptr); // removes itself from mChildren via removeChild() if (mParentPlot->currentLayer() == this) qDebug() << Q_FUNC_INFO << "The parent plot's mCurrentLayer will be a dangling pointer. Should have been set to a valid layer " "or 0 beforehand."; } /*! Sets whether this layer is visible or not. If \a visible is set to false, all layerables on this layer will be invisible. This function doesn't change the visibility property of the layerables (\ref QCPLayerable::setVisible), but the \ref QCPLayerable::realVisibility of each layerable takes the visibility of the parent layer into account. */ void QCPLayer::setVisible(bool visible) { mVisible = visible; } /*! Sets the rendering mode of this layer. If \a mode is set to \ref lmBuffered for a layer, it will be given a dedicated paint buffer by the parent QCustomPlot instance. This means it may be replotted individually by calling \ref QCPLayer::replot, without needing to replot all other layers. Layers which are set to \ref lmLogical (the default) are used only to define the rendering order and can't be replotted individually. Note that each layer which is set to \ref lmBuffered requires additional paint buffers for the layers below, above and for the layer itself. This increases the memory consumption and (slightly) decreases the repainting speed because multiple paint buffers need to be joined. So you should carefully choose which layers benefit from having their own paint buffer. A typical example would be a layer which contains certain layerables (e.g. items) that need to be changed and thus replotted regularly, while all other layerables on other layers stay static. By default, only the topmost layer called "overlay" is in mode \ref lmBuffered, and contains the selection rect. \see replot */ void QCPLayer::setMode(QCPLayer::LayerMode mode) { if (mMode != mode) { mMode = mode; if (!mPaintBuffer.isNull()) mPaintBuffer.data()->setInvalidated(); } } /*! \internal Draws the contents of this layer with the provided \a painter. \see replot, drawToPaintBuffer */ void QCPLayer::draw(QCPPainter *painter) { foreach (QCPLayerable *child, mChildren) { if (child->realVisibility()) { painter->save(); painter->setClipRect(child->clipRect().translated(0, -1)); child->applyDefaultAntialiasingHint(painter); child->draw(painter); painter->restore(); } } } /*! \internal Draws the contents of this layer into the paint buffer which is associated with this layer. The association is established by the parent QCustomPlot, which manages all paint buffers (see \ref QCustomPlot::setupPaintBuffers). \see draw */ void QCPLayer::drawToPaintBuffer() { if (!mPaintBuffer.isNull()) { if (QCPPainter *painter = mPaintBuffer.data()->startPainting()) { if (painter->isActive()) draw(painter); else qDebug() << Q_FUNC_INFO << "paint buffer returned inactive painter"; delete painter; mPaintBuffer.data()->donePainting(); } else qDebug() << Q_FUNC_INFO << "paint buffer returned zero painter"; } else qDebug() << Q_FUNC_INFO << "no valid paint buffer associated with this layer"; } /*! If the layer mode (\ref setMode) is set to \ref lmBuffered, this method allows replotting only the layerables on this specific layer, without the need to replot all other layers (as a call to \ref QCustomPlot::replot would do). If the layer mode is \ref lmLogical however, this method simply calls \ref QCustomPlot::replot on the parent QCustomPlot instance. QCustomPlot also makes sure to replot all layers instead of only this one, if the layer ordering has changed since the last full replot and the other paint buffers were thus invalidated. \see draw */ void QCPLayer::replot() { if (mMode == lmBuffered && !mParentPlot->hasInvalidatedPaintBuffers()) { if (!mPaintBuffer.isNull()) { mPaintBuffer.data()->clear(Qt::transparent); drawToPaintBuffer(); mPaintBuffer.data()->setInvalidated(false); mParentPlot->update(); } else qDebug() << Q_FUNC_INFO << "no valid paint buffer associated with this layer"; } else if (mMode == lmLogical) mParentPlot->replot(); } /*! \internal Adds the \a layerable to the list of this layer. If \a prepend is set to true, the layerable will be prepended to the list, i.e. be drawn beneath the other layerables already in the list. This function does not change the \a mLayer member of \a layerable to this layer. (Use QCPLayerable::setLayer to change the layer of an object, not this function.) \see removeChild */ void QCPLayer::addChild(QCPLayerable *layerable, bool prepend) { if (!mChildren.contains(layerable)) { if (prepend) mChildren.prepend(layerable); else mChildren.append(layerable); if (!mPaintBuffer.isNull()) mPaintBuffer.data()->setInvalidated(); } else qDebug() << Q_FUNC_INFO << "layerable is already child of this layer" << reinterpret_cast(layerable); } /*! \internal Removes the \a layerable from the list of this layer. This function does not change the \a mLayer member of \a layerable. (Use QCPLayerable::setLayer to change the layer of an object, not this function.) \see addChild */ void QCPLayer::removeChild(QCPLayerable *layerable) { if (mChildren.removeOne(layerable)) { if (!mPaintBuffer.isNull()) mPaintBuffer.data()->setInvalidated(); } else qDebug() << Q_FUNC_INFO << "layerable is not child of this layer" << reinterpret_cast(layerable); } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPLayerable //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPLayerable \brief Base class for all drawable objects This is the abstract base class most visible objects derive from, e.g. plottables, axes, grid etc. Every layerable is on a layer (QCPLayer) which allows controlling the rendering order by stacking the layers accordingly. For details about the layering mechanism, see the QCPLayer documentation. */ /* start documentation of inline functions */ /*! \fn QCPLayerable *QCPLayerable::parentLayerable() const Returns the parent layerable of this layerable. The parent layerable is used to provide visibility hierarchies in conjunction with the method \ref realVisibility. This way, layerables only get drawn if their parent layerables are visible, too. Note that a parent layerable is not necessarily also the QObject parent for memory management. Further, a layerable doesn't always have a parent layerable, so this function may return 0. A parent layerable is set implicitly when placed inside layout elements and doesn't need to be set manually by the user. */ /* end documentation of inline functions */ /* start documentation of pure virtual functions */ /*! \fn virtual void QCPLayerable::applyDefaultAntialiasingHint(QCPPainter *painter) const = 0 \internal This function applies the default antialiasing setting to the specified \a painter, using the function \ref applyAntialiasingHint. It is the antialiasing state the painter is put in, when \ref draw is called on the layerable. If the layerable has multiple entities whose antialiasing setting may be specified individually, this function should set the antialiasing state of the most prominent entity. In this case however, the \ref draw function usually calls the specialized versions of this function before drawing each entity, effectively overriding the setting of the default antialiasing hint. First example: QCPGraph has multiple entities that have an antialiasing setting: The graph line, fills and scatters. Those can be configured via QCPGraph::setAntialiased, QCPGraph::setAntialiasedFill and QCPGraph::setAntialiasedScatters. Consequently, there isn't only the QCPGraph::applyDefaultAntialiasingHint function (which corresponds to the graph line's antialiasing), but specialized ones like QCPGraph::applyFillAntialiasingHint and QCPGraph::applyScattersAntialiasingHint. So before drawing one of those entities, QCPGraph::draw calls the respective specialized applyAntialiasingHint function. Second example: QCPItemLine consists only of a line so there is only one antialiasing setting which can be controlled with QCPItemLine::setAntialiased. (This function is inherited by all layerables. The specialized functions, as seen on QCPGraph, must be added explicitly to the respective layerable subclass.) Consequently it only has the normal QCPItemLine::applyDefaultAntialiasingHint. The \ref QCPItemLine::draw function doesn't need to care about setting any antialiasing states, because the default antialiasing hint is already set on the painter when the \ref draw function is called, and that's the state it wants to draw the line with. */ /*! \fn virtual void QCPLayerable::draw(QCPPainter *painter) const = 0 \internal This function draws the layerable with the specified \a painter. It is only called by QCustomPlot, if the layerable is visible (\ref setVisible). Before this function is called, the painter's antialiasing state is set via \ref applyDefaultAntialiasingHint, see the documentation there. Further, the clipping rectangle was set to \ref clipRect. */ /* end documentation of pure virtual functions */ /* start documentation of signals */ /*! \fn void QCPLayerable::layerChanged(QCPLayer *newLayer); This signal is emitted when the layer of this layerable changes, i.e. this layerable is moved to a different layer. \see setLayer */ /* end documentation of signals */ /*! Creates a new QCPLayerable instance. Since QCPLayerable is an abstract base class, it can't be instantiated directly. Use one of the derived classes. If \a plot is provided, it automatically places itself on the layer named \a targetLayer. If \a targetLayer is an empty string, it places itself on the current layer of the plot (see \ref QCustomPlot::setCurrentLayer). It is possible to provide 0 as \a plot. In that case, you should assign a parent plot at a later time with \ref initializeParentPlot. The layerable's parent layerable is set to \a parentLayerable, if provided. Direct layerable parents are mainly used to control visibility in a hierarchy of layerables. This means a layerable is only drawn, if all its ancestor layerables are also visible. Note that \a parentLayerable does not become the QObject-parent (for memory management) of this layerable, \a plot does. It is not uncommon to set the QObject-parent to something else in the constructors of QCPLayerable subclasses, to guarantee a working destruction hierarchy. */ QCPLayerable::QCPLayerable(QCustomPlot *plot, QString targetLayer, QCPLayerable *parentLayerable) - : QObject(plot), mVisible(true), mParentPlot(plot), mParentLayerable(parentLayerable), mLayer(0), mAntialiased(true) + : QObject(plot), mVisible(true), mParentPlot(plot), mParentLayerable(parentLayerable), mLayer(nullptr), mAntialiased(true) { if (mParentPlot) { if (targetLayer.isEmpty()) setLayer(mParentPlot->currentLayer()); else if (!setLayer(targetLayer)) qDebug() << Q_FUNC_INFO << "setting QCPlayerable initial layer to" << targetLayer << "failed."; } } QCPLayerable::~QCPLayerable() { if (mLayer) { mLayer->removeChild(this); - mLayer = 0; + mLayer = nullptr; } } /*! Sets the visibility of this layerable object. If an object is not visible, it will not be drawn on the QCustomPlot surface, and user interaction with it (e.g. click and selection) is not possible. */ void QCPLayerable::setVisible(bool on) { mVisible = on; } /*! Sets the \a layer of this layerable object. The object will be placed on top of the other objects already on \a layer. If \a layer is 0, this layerable will not be on any layer and thus not appear in the plot (or interact/receive events). Returns true if the layer of this layerable was successfully changed to \a layer. */ bool QCPLayerable::setLayer(QCPLayer *layer) { return moveToLayer(layer, false); } /*! \overload Sets the layer of this layerable object by name Returns true on success, i.e. if \a layerName is a valid layer name. */ bool QCPLayerable::setLayer(const QString &layerName) { if (!mParentPlot) { qDebug() << Q_FUNC_INFO << "no parent QCustomPlot set"; return false; } if (QCPLayer *layer = mParentPlot->layer(layerName)) { return setLayer(layer); } else { qDebug() << Q_FUNC_INFO << "there is no layer with name" << layerName; return false; } } /*! Sets whether this object will be drawn antialiased or not. Note that antialiasing settings may be overridden by QCustomPlot::setAntialiasedElements and QCustomPlot::setNotAntialiasedElements. */ void QCPLayerable::setAntialiased(bool enabled) { mAntialiased = enabled; } /*! Returns whether this layerable is visible, taking the visibility of the layerable parent and the visibility of this layerable's layer into account. This is the method that is consulted to decide whether a layerable shall be drawn or not. If this layerable has a direct layerable parent (usually set via hierarchies implemented in subclasses, like in the case of \ref QCPLayoutElement), this function returns true only if this layerable has its visibility set to true and the parent layerable's \ref realVisibility returns true. */ bool QCPLayerable::realVisibility() const { return mVisible && (!mLayer || mLayer->visible()) && (!mParentLayerable || mParentLayerable.data()->realVisibility()); } /*! This function is used to decide whether a click hits a layerable object or not. \a pos is a point in pixel coordinates on the QCustomPlot surface. This function returns the shortest pixel distance of this point to the object. If the object is either invisible or the distance couldn't be determined, -1.0 is returned. Further, if \a onlySelectable is true and the object is not selectable, -1.0 is returned, too. If the object is represented not by single lines but by an area like a \ref QCPItemText or the bars of a \ref QCPBars plottable, a click inside the area should also be considered a hit. In these cases this function thus returns a constant value greater zero but still below the parent plot's selection tolerance. (typically the selectionTolerance multiplied by 0.99). Providing a constant value for area objects allows selecting line objects even when they are obscured by such area objects, by clicking close to the lines (i.e. closer than 0.99*selectionTolerance). The actual setting of the selection state is not done by this function. This is handled by the parent QCustomPlot when the mouseReleaseEvent occurs, and the finally selected object is notified via the \ref selectEvent/\ref deselectEvent methods. \a details is an optional output parameter. Every layerable subclass may place any information in \a details. This information will be passed to \ref selectEvent when the parent QCustomPlot decides on the basis of this selectTest call, that the object was successfully selected. The subsequent call to \ref selectEvent will carry the \a details. This is useful for multi-part objects (like QCPAxis). This way, a possibly complex calculation to decide which part was clicked is only done once in \ref selectTest. The result (i.e. the actually clicked part) can then be placed in \a details. So in the subsequent \ref selectEvent, the decision which part was selected doesn't have to be done a second time for a single selection operation. You may pass 0 as \a details to indicate that you are not interested in those selection details. \see selectEvent, deselectEvent, mousePressEvent, wheelEvent, QCustomPlot::setInteractions */ double QCPLayerable::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(pos) Q_UNUSED(onlySelectable) Q_UNUSED(details) return -1.0; } /*! \internal Sets the parent plot of this layerable. Use this function once to set the parent plot if you have passed 0 in the constructor. It can not be used to move a layerable from one QCustomPlot to another one. Note that, unlike when passing a non-null parent plot in the constructor, this function does not make \a parentPlot the QObject-parent of this layerable. If you want this, call QObject::setParent(\a parentPlot) in addition to this function. Further, you will probably want to set a layer (\ref setLayer) after calling this function, to make the layerable appear on the QCustomPlot. The parent plot change will be propagated to subclasses via a call to \ref parentPlotInitialized so they can react accordingly (e.g. also initialize the parent plot of child layerables, like QCPLayout does). */ void QCPLayerable::initializeParentPlot(QCustomPlot *parentPlot) { if (mParentPlot) { qDebug() << Q_FUNC_INFO << "called with mParentPlot already initialized"; return; } if (!parentPlot) qDebug() << Q_FUNC_INFO << "called with parentPlot zero"; mParentPlot = parentPlot; parentPlotInitialized(mParentPlot); } /*! \internal Sets the parent layerable of this layerable to \a parentLayerable. Note that \a parentLayerable does not become the QObject-parent (for memory management) of this layerable. The parent layerable has influence on the return value of the \ref realVisibility method. Only layerables with a fully visible parent tree will return true for \ref realVisibility, and thus be drawn. \see realVisibility */ void QCPLayerable::setParentLayerable(QCPLayerable *parentLayerable) { mParentLayerable = parentLayerable; } /*! \internal Moves this layerable object to \a layer. If \a prepend is true, this object will be prepended to the new layer's list, i.e. it will be drawn below the objects already on the layer. If it is false, the object will be appended. Returns true on success, i.e. if \a layer is a valid layer. */ bool QCPLayerable::moveToLayer(QCPLayer *layer, bool prepend) { if (layer && !mParentPlot) { qDebug() << Q_FUNC_INFO << "no parent QCustomPlot set"; return false; } if (layer && layer->parentPlot() != mParentPlot) { qDebug() << Q_FUNC_INFO << "layer" << layer->name() << "is not in same QCustomPlot as this layerable"; return false; } QCPLayer *oldLayer = mLayer; if (mLayer) mLayer->removeChild(this); mLayer = layer; if (mLayer) mLayer->addChild(this, prepend); if (mLayer != oldLayer) emit layerChanged(mLayer); return true; } /*! \internal Sets the QCPainter::setAntialiasing state on the provided \a painter, depending on the \a localAntialiased value as well as the overrides \ref QCustomPlot::setAntialiasedElements and \ref QCustomPlot::setNotAntialiasedElements. Which override enum this function takes into account is controlled via \a overrideElement. */ void QCPLayerable::applyAntialiasingHint(QCPPainter *painter, bool localAntialiased, QCP::AntialiasedElement overrideElement) const { if (mParentPlot && mParentPlot->notAntialiasedElements().testFlag(overrideElement)) painter->setAntialiasing(false); else if (mParentPlot && mParentPlot->antialiasedElements().testFlag(overrideElement)) painter->setAntialiasing(true); else painter->setAntialiasing(localAntialiased); } /*! \internal This function is called by \ref initializeParentPlot, to allow subclasses to react on the setting of a parent plot. This is the case when 0 was passed as parent plot in the constructor, and the parent plot is set at a later time. For example, QCPLayoutElement/QCPLayout hierarchies may be created independently of any QCustomPlot at first. When they are then added to a layout inside the QCustomPlot, the top level element of the hierarchy gets its parent plot initialized with \ref initializeParentPlot. To propagate the parent plot to all the children of the hierarchy, the top level element then uses this function to pass the parent plot on to its child elements. The default implementation does nothing. \see initializeParentPlot */ void QCPLayerable::parentPlotInitialized(QCustomPlot *parentPlot){ Q_UNUSED(parentPlot) } /*! \internal Returns the selection category this layerable shall belong to. The selection category is used in conjunction with \ref QCustomPlot::setInteractions to control which objects are selectable and which aren't. Subclasses that don't fit any of the normal \ref QCP::Interaction values can use \ref QCP::iSelectOther. This is what the default implementation returns. \see QCustomPlot::setInteractions */ QCP::Interaction QCPLayerable::selectionCategory() const { return QCP::iSelectOther; } /*! \internal Returns the clipping rectangle of this layerable object. By default, this is the viewport of the parent QCustomPlot. Specific subclasses may reimplement this function to provide different clipping rects. The returned clipping rect is set on the painter before the draw function of the respective object is called. */ QRect QCPLayerable::clipRect() const { if (mParentPlot) return mParentPlot->viewport(); else return QRect(); } /*! \internal This event is called when the layerable shall be selected, as a consequence of a click by the user. Subclasses should react to it by setting their selection state appropriately. The default implementation does nothing. \a event is the mouse event that caused the selection. \a additive indicates, whether the user was holding the multi-select-modifier while performing the selection (see \ref QCustomPlot::setMultiSelectModifier). if \a additive is true, the selection state must be toggled (i.e. become selected when unselected and unselected when selected). Every selectEvent is preceded by a call to \ref selectTest, which has returned positively (i.e. returned a value greater than 0 and less than the selection tolerance of the parent QCustomPlot). The \a details data you output from \ref selectTest is fed back via \a details here. You may use it to transport any kind of information from the selectTest to the possibly subsequent selectEvent. Usually \a details is used to transfer which part was clicked, if it is a layerable that has multiple individually selectable parts (like QCPAxis). This way selectEvent doesn't need to do the calculation again to find out which part was actually clicked. \a selectionStateChanged is an output parameter. If the pointer is non-null, this function must set the value either to true or false, depending on whether the selection state of this layerable was actually changed. For layerables that only are selectable as a whole and not in parts, this is simple: if \a additive is true, \a selectionStateChanged must also be set to true, because the selection toggles. If \a additive is false, \a selectionStateChanged is only set to true, if the layerable was previously unselected and now is switched to the selected state. \see selectTest, deselectEvent */ void QCPLayerable::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) { Q_UNUSED(event) Q_UNUSED(additive) Q_UNUSED(details) Q_UNUSED(selectionStateChanged) } /*! \internal This event is called when the layerable shall be deselected, either as consequence of a user interaction or a call to \ref QCustomPlot::deselectAll. Subclasses should react to it by unsetting their selection appropriately. just as in \ref selectEvent, the output parameter \a selectionStateChanged (if non-null), must return true or false when the selection state of this layerable has changed or not changed, respectively. \see selectTest, selectEvent */ void QCPLayerable::deselectEvent(bool *selectionStateChanged) { Q_UNUSED(selectionStateChanged) } /*! This event gets called when the user presses a mouse button while the cursor is over the layerable. Whether a cursor is over the layerable is decided by a preceding call to \ref selectTest. The current pixel position of the cursor on the QCustomPlot widget is accessible via \c event->pos(). The parameter \a details contains layerable-specific details about the hit, which were generated in the previous call to \ref selectTest. For example, One-dimensional plottables like \ref QCPGraph or \ref QCPBars convey the clicked data point in the \a details parameter, as \ref QCPDataSelection packed as QVariant. Multi-part objects convey the specific \c SelectablePart that was hit (e.g. \ref QCPAxis::SelectablePart in the case of axes). QCustomPlot uses an event propagation system that works the same as Qt's system. If your layerable doesn't reimplement the \ref mousePressEvent or explicitly calls \c event->ignore() in its reimplementation, the event will be propagated to the next layerable in the stacking order. Once a layerable has accepted the \ref mousePressEvent, it is considered the mouse grabber and will receive all following calls to \ref mouseMoveEvent or \ref mouseReleaseEvent for this mouse interaction (a "mouse interaction" in this context ends with the release). The default implementation does nothing except explicitly ignoring the event with \c event->ignore(). \see mouseMoveEvent, mouseReleaseEvent, mouseDoubleClickEvent, wheelEvent */ void QCPLayerable::mousePressEvent(QMouseEvent *event, const QVariant &details) { Q_UNUSED(details) event->ignore(); } /*! This event gets called when the user moves the mouse while holding a mouse button, after this layerable has become the mouse grabber by accepting the preceding \ref mousePressEvent. The current pixel position of the cursor on the QCustomPlot widget is accessible via \c event->pos(). The parameter \a startPos indicates the position where the initial \ref mousePressEvent occurred, that started the mouse interaction. The default implementation does nothing. \see mousePressEvent, mouseReleaseEvent, mouseDoubleClickEvent, wheelEvent */ void QCPLayerable::mouseMoveEvent(QMouseEvent *event, const QPointF &startPos) { Q_UNUSED(startPos) event->ignore(); } /*! This event gets called when the user releases the mouse button, after this layerable has become the mouse grabber by accepting the preceding \ref mousePressEvent. The current pixel position of the cursor on the QCustomPlot widget is accessible via \c event->pos(). The parameter \a startPos indicates the position where the initial \ref mousePressEvent occurred, that started the mouse interaction. The default implementation does nothing. \see mousePressEvent, mouseMoveEvent, mouseDoubleClickEvent, wheelEvent */ void QCPLayerable::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) { Q_UNUSED(startPos) event->ignore(); } /*! This event gets called when the user presses the mouse button a second time in a double-click, while the cursor is over the layerable. Whether a cursor is over the layerable is decided by a preceding call to \ref selectTest. The \ref mouseDoubleClickEvent is called instead of the second \ref mousePressEvent. So in the case of a double-click, the event succession is pressEvent – releaseEvent – doubleClickEvent – releaseEvent. The current pixel position of the cursor on the QCustomPlot widget is accessible via \c event->pos(). The parameter \a details contains layerable-specific details about the hit, which were generated in the previous call to \ref selectTest. For example, One-dimensional plottables like \ref QCPGraph or \ref QCPBars convey the clicked data point in the \a details parameter, as \ref QCPDataSelection packed as QVariant. Multi-part objects convey the specific \c SelectablePart that was hit (e.g. \ref QCPAxis::SelectablePart in the case of axes). Similarly to \ref mousePressEvent, once a layerable has accepted the \ref mouseDoubleClickEvent, it is considered the mouse grabber and will receive all following calls to \ref mouseMoveEvent and \ref mouseReleaseEvent for this mouse interaction (a "mouse interaction" in this context ends with the release). The default implementation does nothing except explicitly ignoring the event with \c event->ignore(). \see mousePressEvent, mouseMoveEvent, mouseReleaseEvent, wheelEvent */ void QCPLayerable::mouseDoubleClickEvent(QMouseEvent *event, const QVariant &details) { Q_UNUSED(details) event->ignore(); } /*! This event gets called when the user turns the mouse scroll wheel while the cursor is over the layerable. Whether a cursor is over the layerable is decided by a preceding call to \ref selectTest. The current pixel position of the cursor on the QCustomPlot widget is accessible via \c event->pos(). The \c event->delta() indicates how far the mouse wheel was turned, which is usually +/- 120 for single rotation steps. However, if the mouse wheel is turned rapidly, multiple steps may accumulate to one event, making \c event->delta() larger. On the other hand, if the wheel has very smooth steps or none at all, the delta may be smaller. The default implementation does nothing. \see mousePressEvent, mouseMoveEvent, mouseReleaseEvent, mouseDoubleClickEvent */ void QCPLayerable::wheelEvent(QWheelEvent *event) { event->ignore(); } /* end of 'src/layer.cpp' */ /* including file 'src/axis/range.cpp', size 12221 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPRange //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPRange \brief Represents the range an axis is encompassing. contains a \a lower and \a upper double value and provides convenience input, output and modification functions. \see QCPAxis::setRange */ /* start of documentation of inline functions */ /*! \fn double QCPRange::size() const Returns the size of the range, i.e. \a upper-\a lower */ /*! \fn double QCPRange::center() const Returns the center of the range, i.e. (\a upper+\a lower)*0.5 */ /*! \fn void QCPRange::normalize() Makes sure \a lower is numerically smaller than \a upper. If this is not the case, the values are swapped. */ /*! \fn bool QCPRange::contains(double value) const Returns true when \a value lies within or exactly on the borders of the range. */ /*! \fn QCPRange &QCPRange::operator+=(const double& value) Adds \a value to both boundaries of the range. */ /*! \fn QCPRange &QCPRange::operator-=(const double& value) Subtracts \a value from both boundaries of the range. */ /*! \fn QCPRange &QCPRange::operator*=(const double& value) Multiplies both boundaries of the range by \a value. */ /*! \fn QCPRange &QCPRange::operator/=(const double& value) Divides both boundaries of the range by \a value. */ /* end of documentation of inline functions */ /*! Minimum range size (\a upper - \a lower) the range changing functions will accept. Smaller intervals would cause errors due to the 11-bit exponent of double precision numbers, corresponding to a minimum magnitude of roughly 1e-308. \warning Do not use this constant to indicate "arbitrarily small" values in plotting logic (as values that will appear in the plot)! It is intended only as a bound to compare against, e.g. to prevent axis ranges from obtaining underflowing ranges. \see validRange, maxRange */ const double QCPRange::minRange = 1e-280; /*! Maximum values (negative and positive) the range will accept in range-changing functions. Larger absolute values would cause errors due to the 11-bit exponent of double precision numbers, corresponding to a maximum magnitude of roughly 1e308. \warning Do not use this constant to indicate "arbitrarily large" values in plotting logic (as values that will appear in the plot)! It is intended only as a bound to compare against, e.g. to prevent axis ranges from obtaining overflowing ranges. \see validRange, minRange */ const double QCPRange::maxRange = 1e250; /*! Constructs a range with \a lower and \a upper set to zero. */ QCPRange::QCPRange() : lower(0), upper(0) { } /*! \overload Constructs a range with the specified \a lower and \a upper values. The resulting range will be normalized (see \ref normalize), so if \a lower is not numerically smaller than \a upper, they will be swapped. */ QCPRange::QCPRange(double lower, double upper) : lower(lower), upper(upper) { normalize(); } /*! \overload Expands this range such that \a otherRange is contained in the new range. It is assumed that both this range and \a otherRange are normalized (see \ref normalize). If this range contains NaN as lower or upper bound, it will be replaced by the respective bound of \a otherRange. If \a otherRange is already inside the current range, this function does nothing. \see expanded */ void QCPRange::expand(const QCPRange &otherRange) { if (lower > otherRange.lower || qIsNaN(lower)) lower = otherRange.lower; if (upper < otherRange.upper || qIsNaN(upper)) upper = otherRange.upper; } /*! \overload Expands this range such that \a includeCoord is contained in the new range. It is assumed that this range is normalized (see \ref normalize). If this range contains NaN as lower or upper bound, the respective bound will be set to \a includeCoord. If \a includeCoord is already inside the current range, this function does nothing. \see expand */ void QCPRange::expand(double includeCoord) { if (lower > includeCoord || qIsNaN(lower)) lower = includeCoord; if (upper < includeCoord || qIsNaN(upper)) upper = includeCoord; } /*! \overload Returns an expanded range that contains this and \a otherRange. It is assumed that both this range and \a otherRange are normalized (see \ref normalize). If this range contains NaN as lower or upper bound, the returned range's bound will be taken from \a otherRange. \see expand */ QCPRange QCPRange::expanded(const QCPRange &otherRange) const { QCPRange result = *this; result.expand(otherRange); return result; } /*! \overload Returns an expanded range that includes the specified \a includeCoord. It is assumed that this range is normalized (see \ref normalize). If this range contains NaN as lower or upper bound, the returned range's bound will be set to \a includeCoord. \see expand */ QCPRange QCPRange::expanded(double includeCoord) const { QCPRange result = *this; result.expand(includeCoord); return result; } /*! Returns this range, possibly modified to not exceed the bounds provided as \a lowerBound and \a upperBound. If possible, the size of the current range is preserved in the process. If the range shall only be bounded at the lower side, you can set \a upperBound to \ref QCPRange::maxRange. If it shall only be bounded at the upper side, set \a lowerBound to -\ref QCPRange::maxRange. */ QCPRange QCPRange::bounded(double lowerBound, double upperBound) const { if (lowerBound > upperBound) qSwap(lowerBound, upperBound); QCPRange result(lower, upper); if (result.lower < lowerBound) { result.lower = lowerBound; result.upper = lowerBound + size(); if (result.upper > upperBound || qFuzzyCompare(size(), upperBound - lowerBound)) result.upper = upperBound; } else if (result.upper > upperBound) { result.upper = upperBound; result.lower = upperBound - size(); if (result.lower < lowerBound || qFuzzyCompare(size(), upperBound - lowerBound)) result.lower = lowerBound; } return result; } /*! Returns a sanitized version of the range. Sanitized means for logarithmic scales, that the range won't span the positive and negative sign domain, i.e. contain zero. Further \a lower will always be numerically smaller (or equal) to \a upper. If the original range does span positive and negative sign domains or contains zero, the returned range will try to approximate the original range as good as possible. If the positive interval of the original range is wider than the negative interval, the returned range will only contain the positive interval, with lower bound set to \a rangeFac or \a rangeFac *\a upper, whichever is closer to zero. Same procedure is used if the negative interval is wider than the positive interval, this time by changing the \a upper bound. */ QCPRange QCPRange::sanitizedForLogScale() const { double rangeFac = 1e-3; QCPRange sanitizedRange(lower, upper); sanitizedRange.normalize(); // can't have range spanning negative and positive values in log plot, so change range to fix it //if (qFuzzyCompare(sanitizedRange.lower+1, 1) && !qFuzzyCompare(sanitizedRange.upper+1, 1)) if (sanitizedRange.lower == 0.0 && sanitizedRange.upper != 0.0) { // case lower is 0 if (rangeFac < sanitizedRange.upper * rangeFac) sanitizedRange.lower = rangeFac; else sanitizedRange.lower = sanitizedRange.upper * rangeFac; } //else if (!qFuzzyCompare(lower+1, 1) && qFuzzyCompare(upper+1, 1)) else if (sanitizedRange.lower != 0.0 && sanitizedRange.upper == 0.0) { // case upper is 0 if (-rangeFac > sanitizedRange.lower * rangeFac) sanitizedRange.upper = -rangeFac; else sanitizedRange.upper = sanitizedRange.lower * rangeFac; } else if (sanitizedRange.lower < 0 && sanitizedRange.upper > 0) { // find out whether negative or positive interval is wider to decide which sign domain will be chosen if (-sanitizedRange.lower > sanitizedRange.upper) { // negative is wider, do same as in case upper is 0 if (-rangeFac > sanitizedRange.lower * rangeFac) sanitizedRange.upper = -rangeFac; else sanitizedRange.upper = sanitizedRange.lower * rangeFac; } else { // positive is wider, do same as in case lower is 0 if (rangeFac < sanitizedRange.upper * rangeFac) sanitizedRange.lower = rangeFac; else sanitizedRange.lower = sanitizedRange.upper * rangeFac; } } // due to normalization, case lower>0 && upper<0 should never occur, because that implies upper -maxRange && upper < maxRange && qAbs(lower - upper) > minRange && qAbs(lower - upper) < maxRange && !(lower > 0 && qIsInf(upper / lower)) && !(upper < 0 && qIsInf(lower / upper))); } /*! \overload Checks, whether the specified range is within valid bounds, which are defined as QCPRange::maxRange and QCPRange::minRange. A valid range means: \li range bounds within -maxRange and maxRange \li range size above minRange \li range size below maxRange */ bool QCPRange::validRange(const QCPRange &range) { return (range.lower > -maxRange && range.upper < maxRange && qAbs(range.lower - range.upper) > minRange && qAbs(range.lower - range.upper) < maxRange && !(range.lower > 0 && qIsInf(range.upper / range.lower)) && !(range.upper < 0 && qIsInf(range.lower / range.upper))); } /* end of 'src/axis/range.cpp' */ /* including file 'src/selection.cpp', size 21898 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPDataRange //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPDataRange \brief Describes a data range given by begin and end index QCPDataRange holds two integers describing the begin (\ref setBegin) and end (\ref setEnd) index of a contiguous set of data points. The end index points to the data point above the last data point that's part of the data range, similarly to the nomenclature used in standard iterators. Data Ranges are not bound to a certain plottable, thus they can be freely exchanged, created and modified. If a non-contiguous data set shall be described, the class \ref QCPDataSelection is used, which holds and manages multiple instances of \ref QCPDataRange. In most situations, \ref QCPDataSelection is thus used. Both \ref QCPDataRange and \ref QCPDataSelection offer convenience methods to work with them, e.g. \ref bounded, \ref expanded, \ref intersects, \ref intersection, \ref adjusted, \ref contains. Further, addition and subtraction operators (defined in \ref QCPDataSelection) can be used to join/subtract data ranges and data selections (or mixtures), to retrieve a corresponding \ref QCPDataSelection. %QCustomPlot's \ref dataselection "data selection mechanism" is based on \ref QCPDataSelection and QCPDataRange. \note Do not confuse \ref QCPDataRange with \ref QCPRange. A \ref QCPRange describes an interval in floating point plot coordinates, e.g. the current axis range. */ /* start documentation of inline functions */ /*! \fn int QCPDataRange::size() const Returns the number of data points described by this data range. This is equal to the end index minus the begin index. \see length */ /*! \fn int QCPDataRange::length() const Returns the number of data points described by this data range. Equivalent to \ref size. */ /*! \fn void QCPDataRange::setBegin(int begin) Sets the begin of this data range. The \a begin index points to the first data point that is part of the data range. No checks or corrections are made to ensure the resulting range is valid (\ref isValid). \see setEnd */ /*! \fn void QCPDataRange::setEnd(int end) Sets the end of this data range. The \a end index points to the data point just above the last data point that is part of the data range. No checks or corrections are made to ensure the resulting range is valid (\ref isValid). \see setBegin */ /*! \fn bool QCPDataRange::isValid() const Returns whether this range is valid. A valid range has a begin index greater or equal to 0, and an end index greater or equal to the begin index. \note Invalid ranges should be avoided and are never the result of any of QCustomPlot's methods (unless they are themselves fed with invalid ranges). Do not pass invalid ranges to QCustomPlot's methods. The invalid range is not inherently prevented in QCPDataRange, to allow temporary invalid begin/end values while manipulating the range. An invalid range is not necessarily empty (\ref isEmpty), since its \ref length can be negative and thus non-zero. */ /*! \fn bool QCPDataRange::isEmpty() const Returns whether this range is empty, i.e. whether its begin index equals its end index. \see size, length */ /*! \fn QCPDataRange QCPDataRange::adjusted(int changeBegin, int changeEnd) const Returns a data range where \a changeBegin and \a changeEnd were added to the begin and end indices, respectively. */ /* end documentation of inline functions */ /*! Creates an empty QCPDataRange, with begin and end set to 0. */ QCPDataRange::QCPDataRange() : mBegin(0), mEnd(0) { } /*! Creates a QCPDataRange, initialized with the specified \a begin and \a end. No checks or corrections are made to ensure the resulting range is valid (\ref isValid). */ QCPDataRange::QCPDataRange(int begin, int end) : mBegin(begin), mEnd(end) { } /*! Returns a data range that matches this data range, except that parts exceeding \a other are excluded. This method is very similar to \ref intersection, with one distinction: If this range and the \a other range share no intersection, the returned data range will be empty with begin and end set to the respective boundary side of \a other, at which this range is residing. (\ref intersection would just return a range with begin and end set to 0.) */ QCPDataRange QCPDataRange::bounded(const QCPDataRange &other) const { QCPDataRange result(intersection(other)); if (result .isEmpty()) // no intersection, preserve respective bounding side of otherRange as both begin and end of return value { if (mEnd <= other.mBegin) result = QCPDataRange(other.mBegin, other.mBegin); else result = QCPDataRange(other.mEnd, other.mEnd); } return result; } /*! Returns a data range that contains both this data range as well as \a other. */ QCPDataRange QCPDataRange::expanded(const QCPDataRange &other) const { return QCPDataRange(qMin(mBegin, other.mBegin), qMax(mEnd, other.mEnd)); } /*! Returns the data range which is contained in both this data range and \a other. This method is very similar to \ref bounded, with one distinction: If this range and the \a other range share no intersection, the returned data range will be empty with begin and end set to 0. (\ref bounded would return a range with begin and end set to one of the boundaries of \a other, depending on which side this range is on.) \see QCPDataSelection::intersection */ QCPDataRange QCPDataRange::intersection(const QCPDataRange &other) const { QCPDataRange result(qMax(mBegin, other.mBegin), qMin(mEnd, other.mEnd)); if (result.isValid()) return result; else return QCPDataRange(); } /*! Returns whether this data range and \a other share common data points. \see intersection, contains */ bool QCPDataRange::intersects(const QCPDataRange &other) const { return !((mBegin > other.mBegin && mBegin >= other.mEnd) || (mEnd <= other.mBegin && mEnd < other.mEnd)); } /*! Returns whether all data points described by this data range are also in \a other. \see intersects */ bool QCPDataRange::contains(const QCPDataRange &other) const { return mBegin <= other.mBegin && mEnd >= other.mEnd; } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPDataSelection //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPDataSelection \brief Describes a data set by holding multiple QCPDataRange instances QCPDataSelection manages multiple instances of QCPDataRange in order to represent any (possibly disjoint) set of data selection. The data selection can be modified with addition and subtraction operators which take QCPDataSelection and QCPDataRange instances, as well as methods such as \ref addDataRange and \ref clear. Read access is provided by \ref dataRange, \ref dataRanges, \ref dataRangeCount, etc. The method \ref simplify is used to join directly adjacent or even overlapping QCPDataRange instances. QCPDataSelection automatically simplifies when using the addition/subtraction operators. The only case when \ref simplify is left to the user, is when calling \ref addDataRange, with the parameter \a simplify explicitly set to false. This is useful if many data ranges will be added to the selection successively and the overhead for simplifying after each iteration shall be avoided. In this case, you should make sure to call \ref simplify after completing the operation. Use \ref enforceType to bring the data selection into a state complying with the constraints for selections defined in \ref QCP::SelectionType. %QCustomPlot's \ref dataselection "data selection mechanism" is based on QCPDataSelection and QCPDataRange. \section qcpdataselection-iterating Iterating over a data selection As an example, the following code snippet calculates the average value of a graph's data \ref QCPAbstractPlottable::selection "selection": \snippet documentation/doc-code-snippets/mainwindow.cpp qcpdataselection-iterating-1 */ /* start documentation of inline functions */ /*! \fn int QCPDataSelection::dataRangeCount() const Returns the number of ranges that make up the data selection. The ranges can be accessed by \ref dataRange via their index. \see dataRange, dataPointCount */ /*! \fn QList QCPDataSelection::dataRanges() const Returns all data ranges that make up the data selection. If the data selection is simplified (the usual state of the selection, see \ref simplify), the ranges are sorted by ascending data point index. \see dataRange */ /*! \fn bool QCPDataSelection::isEmpty() const Returns true if there are no data ranges, and thus no data points, in this QCPDataSelection instance. \see dataRangeCount */ /* end documentation of inline functions */ /*! Creates an empty QCPDataSelection. */ QCPDataSelection::QCPDataSelection() { } /*! Creates a QCPDataSelection containing the provided \a range. */ QCPDataSelection::QCPDataSelection(const QCPDataRange &range) { mDataRanges.append(range); } /*! Returns true if this selection is identical (contains the same data ranges with the same begin and end indices) to \a other. Note that both data selections must be in simplified state (the usual state of the selection, see \ref simplify) for this operator to return correct results. */ bool QCPDataSelection::operator==(const QCPDataSelection &other) const { if (mDataRanges.size() != other.mDataRanges.size()) return false; for (int i = 0; i < mDataRanges.size(); ++i) { if (mDataRanges.at(i) != other.mDataRanges.at(i)) return false; } return true; } /*! Adds the data selection of \a other to this data selection, and then simplifies this data selection (see \ref simplify). */ QCPDataSelection &QCPDataSelection::operator+=(const QCPDataSelection &other) { mDataRanges << other.mDataRanges; simplify(); return *this; } /*! Adds the data range \a other to this data selection, and then simplifies this data selection (see \ref simplify). */ QCPDataSelection &QCPDataSelection::operator+=(const QCPDataRange &other) { addDataRange(other); return *this; } /*! Removes all data point indices that are described by \a other from this data range. */ QCPDataSelection &QCPDataSelection::operator-=(const QCPDataSelection &other) { for (int i = 0; i < other.dataRangeCount(); ++i) *this -= other.dataRange(i); return *this; } /*! Removes all data point indices that are described by \a other from this data range. */ QCPDataSelection &QCPDataSelection::operator-=(const QCPDataRange &other) { if (other.isEmpty() || isEmpty()) return *this; simplify(); int i = 0; while (i < mDataRanges.size()) { const int thisBegin = mDataRanges.at(i).begin(); const int thisEnd = mDataRanges.at(i).end(); if (thisBegin >= other.end()) break; // since data ranges are sorted after the simplify() call, no ranges which contain other will come after this if (thisEnd > other.begin()) // ranges which don't fulfill this are entirely before other and can be ignored { if (thisBegin >= other.begin()) // range leading segment is encompassed { if (thisEnd <= other.end()) // range fully encompassed, remove completely { mDataRanges.removeAt(i); continue; } else // only leading segment is encompassed, trim accordingly mDataRanges[i].setBegin(other.end()); } else // leading segment is not encompassed { if (thisEnd <= other.end()) // only trailing segment is encompassed, trim accordingly { mDataRanges[i].setEnd(other.begin()); } else // other lies inside this range, so split range { mDataRanges[i].setEnd(other.begin()); mDataRanges.insert(i + 1, QCPDataRange(other.end(), thisEnd)); break; // since data ranges are sorted (and don't overlap) after simplify() call, we're done here } } } ++i; } return *this; } /*! Returns the total number of data points contained in all data ranges that make up this data selection. */ int QCPDataSelection::dataPointCount() const { int result = 0; for (int i = 0; i < mDataRanges.size(); ++i) result += mDataRanges.at(i).length(); return result; } /*! Returns the data range with the specified \a index. If the data selection is simplified (the usual state of the selection, see \ref simplify), the ranges are sorted by ascending data point index. \see dataRangeCount */ QCPDataRange QCPDataSelection::dataRange(int index) const { if (index >= 0 && index < mDataRanges.size()) { return mDataRanges.at(index); } else { qDebug() << Q_FUNC_INFO << "index out of range:" << index; return QCPDataRange(); } } /*! Returns a \ref QCPDataRange which spans the entire data selection, including possible intermediate segments which are not part of the original data selection. */ QCPDataRange QCPDataSelection::span() const { if (isEmpty()) return QCPDataRange(); else return QCPDataRange(mDataRanges.first().begin(), mDataRanges.last().end()); } /*! Adds the given \a dataRange to this data selection. This is equivalent to the += operator but allows disabling immediate simplification by setting \a simplify to false. This can improve performance if adding a very large amount of data ranges successively. In this case, make sure to call \ref simplify manually, after the operation. */ void QCPDataSelection::addDataRange(const QCPDataRange &dataRange, bool simplify) { mDataRanges.append(dataRange); if (simplify) this->simplify(); } /*! Removes all data ranges. The data selection then contains no data points. \ref isEmpty */ void QCPDataSelection::clear() { mDataRanges.clear(); } /*! Sorts all data ranges by range begin index in ascending order, and then joins directly adjacent or overlapping ranges. This can reduce the number of individual data ranges in the selection, and prevents possible double-counting when iterating over the data points held by the data ranges. This method is automatically called when using the addition/subtraction operators. The only case when \ref simplify is left to the user, is when calling \ref addDataRange, with the parameter \a simplify explicitly set to false. */ void QCPDataSelection::simplify() { // remove any empty ranges: for (int i = mDataRanges.size() - 1; i >= 0; --i) { if (mDataRanges.at(i).isEmpty()) mDataRanges.removeAt(i); } if (mDataRanges.isEmpty()) return; // sort ranges by starting value, ascending: std::sort(mDataRanges.begin(), mDataRanges.end(), lessThanDataRangeBegin); // join overlapping/contiguous ranges: int i = 1; while (i < mDataRanges.size()) { if (mDataRanges.at(i - 1).end() >= mDataRanges.at(i) .begin()) // range i overlaps/joins with i-1, so expand range i-1 appropriately and remove range i from list { mDataRanges[i - 1].setEnd(qMax(mDataRanges.at(i - 1).end(), mDataRanges.at(i).end())); mDataRanges.removeAt(i); } else ++i; } } /*! Makes sure this data selection conforms to the specified \a type selection type. Before the type is enforced, \ref simplify is called. Depending on \a type, enforcing means adding new data points that were previously not part of the selection, or removing data points from the selection. If the current selection already conforms to \a type, the data selection is not changed. \see QCP::SelectionType */ void QCPDataSelection::enforceType(QCP::SelectionType type) { simplify(); switch (type) { case QCP::stNone: { mDataRanges.clear(); break; } case QCP::stWhole: { // whole selection isn't defined by data range, so don't change anything (is handled in plottable methods) break; } case QCP::stSingleData: { // reduce all data ranges to the single first data point: if (!mDataRanges.isEmpty()) { if (mDataRanges.size() > 1) mDataRanges = QList() << mDataRanges.first(); if (mDataRanges.first().length() > 1) mDataRanges.first().setEnd(mDataRanges.first().begin() + 1); } break; } case QCP::stDataRange: { mDataRanges = QList() << span(); break; } case QCP::stMultipleDataRanges: { // this is the selection type that allows all concievable combinations of ranges, so do nothing break; } } } /*! Returns true if the data selection \a other is contained entirely in this data selection, i.e. all data point indices that are in \a other are also in this data selection. \see QCPDataRange::contains */ bool QCPDataSelection::contains(const QCPDataSelection &other) const { if (other.isEmpty()) return false; int otherIndex = 0; int thisIndex = 0; while (thisIndex < mDataRanges.size() && otherIndex < other.mDataRanges.size()) { if (mDataRanges.at(thisIndex).contains(other.mDataRanges.at(otherIndex))) ++otherIndex; else ++thisIndex; } return thisIndex < mDataRanges .size(); // if thisIndex ran all the way to the end to find a containing range for the current otherIndex, other is not contained in this } /*! Returns a data selection containing the points which are both in this data selection and in the data range \a other. A common use case is to limit an unknown data selection to the valid range of a data container, using \ref QCPDataContainer::dataRange as \a other. One can then safely iterate over the returned data selection without exceeding the data container's bounds. */ QCPDataSelection QCPDataSelection::intersection(const QCPDataRange &other) const { QCPDataSelection result; for (int i = 0; i < mDataRanges.size(); ++i) result.addDataRange(mDataRanges.at(i).intersection(other), false); result.simplify(); return result; } /*! Returns a data selection containing the points which are both in this data selection and in the data selection \a other. */ QCPDataSelection QCPDataSelection::intersection(const QCPDataSelection &other) const { QCPDataSelection result; for (int i = 0; i < other.dataRangeCount(); ++i) result += intersection(other.dataRange(i)); result.simplify(); return result; } /*! Returns a data selection which is the exact inverse of this data selection, with \a outerRange defining the base range on which to invert. If \a outerRange is smaller than the \ref span of this data selection, it is expanded accordingly. For example, this method can be used to retrieve all unselected segments by setting \a outerRange to the full data range of the plottable, and calling this method on a data selection holding the selected segments. */ QCPDataSelection QCPDataSelection::inverse(const QCPDataRange &outerRange) const { if (isEmpty()) return QCPDataSelection(outerRange); QCPDataRange fullRange = outerRange.expanded(span()); QCPDataSelection result; // first unselected segment: if (mDataRanges.first().begin() != fullRange.begin()) result.addDataRange(QCPDataRange(fullRange.begin(), mDataRanges.first().begin()), false); // intermediate unselected segments: for (int i = 1; i < mDataRanges.size(); ++i) result.addDataRange(QCPDataRange(mDataRanges.at(i - 1).end(), mDataRanges.at(i).begin()), false); // last unselected segment: if (mDataRanges.last().end() != fullRange.end()) result.addDataRange(QCPDataRange(mDataRanges.last().end(), fullRange.end()), false); result.simplify(); return result; } /* end of 'src/selection.cpp' */ /* including file 'src/selectionrect.cpp', size 9224 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPSelectionRect //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPSelectionRect \brief Provides rect/rubber-band data selection and range zoom interaction QCPSelectionRect is used by QCustomPlot when the \ref QCustomPlot::setSelectionRectMode is not \ref QCP::srmNone. When the user drags the mouse across the plot, the current selection rect instance (\ref QCustomPlot::setSelectionRect) is forwarded these events and makes sure an according rect shape is drawn. At the begin, during, and after completion of the interaction, it emits the corresponding signals \ref started, \ref changed, \ref canceled, and \ref accepted. The QCustomPlot instance connects own slots to the current selection rect instance, in order to react to an accepted selection rect interaction accordingly. \ref isActive can be used to check whether the selection rect is currently active. An ongoing selection interaction can be cancelled programmatically via calling \ref cancel at any time. The appearance of the selection rect can be controlled via \ref setPen and \ref setBrush. If you wish to provide custom behaviour, e.g. a different visual representation of the selection rect (\ref QCPSelectionRect::draw), you can subclass QCPSelectionRect and pass an instance of your subclass to \ref QCustomPlot::setSelectionRect. */ /* start of documentation of inline functions */ /*! \fn bool QCPSelectionRect::isActive() const Returns true if there is currently a selection going on, i.e. the user has started dragging a selection rect, but hasn't released the mouse button yet. \see cancel */ /* end of documentation of inline functions */ /* start documentation of signals */ /*! \fn void QCPSelectionRect::started(QMouseEvent *event); This signal is emitted when a selection rect interaction was initiated, i.e. the user just started dragging the selection rect with the mouse. */ /*! \fn void QCPSelectionRect::changed(const QRect &rect, QMouseEvent *event); This signal is emitted while the selection rect interaction is ongoing and the \a rect has changed its size due to the user moving the mouse. Note that \a rect may have a negative width or height, if the selection is being dragged to the upper or left side of the selection rect origin. */ /*! \fn void QCPSelectionRect::canceled(const QRect &rect, QInputEvent *event); This signal is emitted when the selection interaction was cancelled. Note that \a event is 0 if the selection interaction was cancelled programmatically, by a call to \ref cancel. The user may cancel the selection interaction by pressing the escape key. In this case, \a event holds the respective input event. Note that \a rect may have a negative width or height, if the selection is being dragged to the upper or left side of the selection rect origin. */ /*! \fn void QCPSelectionRect::accepted(const QRect &rect, QMouseEvent *event); This signal is emitted when the selection interaction was completed by the user releasing the mouse button. Note that \a rect may have a negative width or height, if the selection is being dragged to the upper or left side of the selection rect origin. */ /* end documentation of signals */ /*! Creates a new QCPSelectionRect instance. To make QCustomPlot use the selection rect instance, pass it to \ref QCustomPlot::setSelectionRect. \a parentPlot should be set to the same QCustomPlot widget. */ QCPSelectionRect::QCPSelectionRect(QCustomPlot *parentPlot) : QCPLayerable(parentPlot), mPen(QBrush(Qt::gray), 0, Qt::DashLine), mBrush(Qt::NoBrush), mActive(false) { } QCPSelectionRect::~QCPSelectionRect() { cancel(); } /*! A convenience function which returns the coordinate range of the provided \a axis, that this selection rect currently encompasses. */ QCPRange QCPSelectionRect::range(const QCPAxis *axis) const { if (axis) { if (axis->orientation() == Qt::Horizontal) return QCPRange(axis->pixelToCoord(mRect.left()), axis->pixelToCoord(mRect.left() + mRect.width())); else return QCPRange(axis->pixelToCoord(mRect.top() + mRect.height()), axis->pixelToCoord(mRect.top())); } else { qDebug() << Q_FUNC_INFO << "called with axis zero"; return QCPRange(); } } /*! Sets the pen that will be used to draw the selection rect outline. \see setBrush */ void QCPSelectionRect::setPen(const QPen &pen) { mPen = pen; } /*! Sets the brush that will be used to fill the selection rect. By default the selection rect is not filled, i.e. \a brush is Qt::NoBrush. \see setPen */ void QCPSelectionRect::setBrush(const QBrush &brush) { mBrush = brush; } /*! If there is currently a selection interaction going on (\ref isActive), the interaction is canceled. The selection rect will emit the \ref canceled signal. */ void QCPSelectionRect::cancel() { if (mActive) { mActive = false; - emit canceled(mRect, 0); + emit canceled(mRect, nullptr); } } /*! \internal This method is called by QCustomPlot to indicate that a selection rect interaction was initiated. The default implementation sets the selection rect to active, initializes the selection rect geometry and emits the \ref started signal. */ void QCPSelectionRect::startSelection(QMouseEvent *event) { mActive = true; mRect = QRect(event->pos(), event->pos()); emit started(event); } /*! \internal This method is called by QCustomPlot to indicate that an ongoing selection rect interaction needs to update its geometry. The default implementation updates the rect and emits the \ref changed signal. */ void QCPSelectionRect::moveSelection(QMouseEvent *event) { mRect.setBottomRight(event->pos()); emit changed(mRect, event); layer()->replot(); } /*! \internal This method is called by QCustomPlot to indicate that an ongoing selection rect interaction has finished by the user releasing the mouse button. The default implementation deactivates the selection rect and emits the \ref accepted signal. */ void QCPSelectionRect::endSelection(QMouseEvent *event) { mRect.setBottomRight(event->pos()); mActive = false; emit accepted(mRect, event); } /*! \internal This method is called by QCustomPlot when a key has been pressed by the user while the selection rect interaction is active. The default implementation allows to \ref cancel the interaction by hitting the escape key. */ void QCPSelectionRect::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape && mActive) { mActive = false; emit canceled(mRect, event); } } /* inherits documentation from base class */ void QCPSelectionRect::applyDefaultAntialiasingHint(QCPPainter *painter) const { applyAntialiasingHint(painter, mAntialiased, QCP::aeOther); } /*! \internal If the selection rect is active (\ref isActive), draws the selection rect defined by \a mRect. \seebaseclassmethod */ void QCPSelectionRect::draw(QCPPainter *painter) { if (mActive) { painter->setPen(mPen); painter->setBrush(mBrush); painter->drawRect(mRect); } } /* end of 'src/selectionrect.cpp' */ /* including file 'src/layout.cpp', size 74302 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPMarginGroup //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPMarginGroup \brief A margin group allows synchronization of margin sides if working with multiple layout elements. QCPMarginGroup allows you to tie a margin side of two or more layout elements together, such that they will all have the same size, based on the largest required margin in the group. \n \image html QCPMarginGroup.png "Demonstration of QCPMarginGroup" \n In certain situations it is desirable that margins at specific sides are synchronized across layout elements. For example, if one QCPAxisRect is below another one in a grid layout, it will provide a cleaner look to the user if the left and right margins of the two axis rects are of the same size. The left axis of the top axis rect will then be at the same horizontal position as the left axis of the lower axis rect, making them appear aligned. The same applies for the right axes. This is what QCPMarginGroup makes possible. To add/remove a specific side of a layout element to/from a margin group, use the \ref QCPLayoutElement::setMarginGroup method. To completely break apart the margin group, either call \ref clear, or just delete the margin group. \section QCPMarginGroup-example Example First create a margin group: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpmargingroup-creation-1 Then set this group on the layout element sides: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpmargingroup-creation-2 Here, we've used the first two axis rects of the plot and synchronized their left margins with each other and their right margins with each other. */ /* start documentation of inline functions */ /*! \fn QList QCPMarginGroup::elements(QCP::MarginSide side) const Returns a list of all layout elements that have their margin \a side associated with this margin group. */ /* end documentation of inline functions */ /*! Creates a new QCPMarginGroup instance in \a parentPlot. */ QCPMarginGroup::QCPMarginGroup(QCustomPlot *parentPlot) : QObject(parentPlot), mParentPlot(parentPlot) { mChildren.insert(QCP::msLeft, QList()); mChildren.insert(QCP::msRight, QList()); mChildren.insert(QCP::msTop, QList()); mChildren.insert(QCP::msBottom, QList()); } QCPMarginGroup::~QCPMarginGroup() { clear(); } /*! Returns whether this margin group is empty. If this function returns true, no layout elements use this margin group to synchronize margin sides. */ bool QCPMarginGroup::isEmpty() const { QHashIterator> it(mChildren); while (it.hasNext()) { it.next(); if (!it.value().isEmpty()) return false; } return true; } /*! Clears this margin group. The synchronization of the margin sides that use this margin group is lifted and they will use their individual margin sizes again. */ void QCPMarginGroup::clear() { // make all children remove themselves from this margin group: QHashIterator> it(mChildren); while (it.hasNext()) { it.next(); const QList elements = it.value(); for (int i = elements.size() - 1; i >= 0; --i) - elements.at(i)->setMarginGroup(it.key(), 0); // removes itself from mChildren via removeChild + elements.at(i)->setMarginGroup(it.key(), nullptr); // removes itself from mChildren via removeChild } } /*! \internal Returns the synchronized common margin for \a side. This is the margin value that will be used by the layout element on the respective side, if it is part of this margin group. The common margin is calculated by requesting the automatic margin (\ref QCPLayoutElement::calculateAutoMargin) of each element associated with \a side in this margin group, and choosing the largest returned value. (QCPLayoutElement::minimumMargins is taken into account, too.) */ int QCPMarginGroup::commonMargin(QCP::MarginSide side) const { // query all automatic margins of the layout elements in this margin group side and find maximum: int result = 0; const QList elements = mChildren.value(side); for (int i = 0; i < elements.size(); ++i) { if (!elements.at(i)->autoMargins().testFlag(side)) continue; int m = qMax(elements.at(i)->calculateAutoMargin(side), QCP::getMarginValue(elements.at(i)->minimumMargins(), side)); if (m > result) result = m; } return result; } /*! \internal Adds \a element to the internal list of child elements, for the margin \a side. This function does not modify the margin group property of \a element. */ void QCPMarginGroup::addChild(QCP::MarginSide side, QCPLayoutElement *element) { if (!mChildren[side].contains(element)) mChildren[side].append(element); else qDebug() << Q_FUNC_INFO << "element is already child of this margin group side" << reinterpret_cast(element); } /*! \internal Removes \a element from the internal list of child elements, for the margin \a side. This function does not modify the margin group property of \a element. */ void QCPMarginGroup::removeChild(QCP::MarginSide side, QCPLayoutElement *element) { if (!mChildren[side].removeOne(element)) qDebug() << Q_FUNC_INFO << "element is not child of this margin group side" << reinterpret_cast(element); } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPLayoutElement //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPLayoutElement \brief The abstract base class for all objects that form \ref thelayoutsystem "the layout system". This is an abstract base class. As such, it can't be instantiated directly, rather use one of its subclasses. A Layout element is a rectangular object which can be placed in layouts. It has an outer rect (QCPLayoutElement::outerRect) and an inner rect (\ref QCPLayoutElement::rect). The difference between outer and inner rect is called its margin. The margin can either be set to automatic or manual (\ref setAutoMargins) on a per-side basis. If a side is set to manual, that margin can be set explicitly with \ref setMargins and will stay fixed at that value. If it's set to automatic, the layout element subclass will control the value itself (via \ref calculateAutoMargin). Layout elements can be placed in layouts (base class QCPLayout) like QCPLayoutGrid. The top level layout is reachable via \ref QCustomPlot::plotLayout, and is a \ref QCPLayoutGrid. Since \ref QCPLayout itself derives from \ref QCPLayoutElement, layouts can be nested. Thus in QCustomPlot one can divide layout elements into two categories: The ones that are invisible by themselves, because they don't draw anything. Their only purpose is to manage the position and size of other layout elements. This category of layout elements usually use QCPLayout as base class. Then there is the category of layout elements which actually draw something. For example, QCPAxisRect, QCPLegend and QCPTextElement are of this category. This does not necessarily mean that the latter category can't have child layout elements. QCPLegend for instance, actually derives from QCPLayoutGrid and the individual legend items are child layout elements in the grid layout. */ /* start documentation of inline functions */ /*! \fn QCPLayout *QCPLayoutElement::layout() const Returns the parent layout of this layout element. */ /*! \fn QRect QCPLayoutElement::rect() const Returns the inner rect of this layout element. The inner rect is the outer rect (\ref setOuterRect) shrinked by the margins (\ref setMargins, \ref setAutoMargins). In some cases, the area between outer and inner rect is left blank. In other cases the margin area is used to display peripheral graphics while the main content is in the inner rect. This is where automatic margin calculation becomes interesting because it allows the layout element to adapt the margins to the peripheral graphics it wants to draw. For example, \ref QCPAxisRect draws the axis labels and tick labels in the margin area, thus needs to adjust the margins (if \ref setAutoMargins is enabled) according to the space required by the labels of the axes. */ /* end documentation of inline functions */ /*! Creates an instance of QCPLayoutElement and sets default values. */ QCPLayoutElement::QCPLayoutElement(QCustomPlot *parentPlot) : QCPLayerable( parentPlot), // parenthood is changed as soon as layout element gets inserted into a layout (except for top level layout) - mParentLayout(0), mMinimumSize(), mMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX), mRect(0, 0, 0, 0), + mParentLayout(nullptr), mMinimumSize(), mMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX), mRect(0, 0, 0, 0), mOuterRect(0, 0, 0, 0), mMargins(0, 0, 0, 0), mMinimumMargins(0, 0, 0, 0), mAutoMargins(QCP::msAll) { } QCPLayoutElement::~QCPLayoutElement() { - setMarginGroup(QCP::msAll, 0); // unregister at margin groups, if there are any + setMarginGroup(QCP::msAll, nullptr); // unregister at margin groups, if there are any // unregister at layout: if (qobject_cast( mParentLayout)) // the qobject_cast is just a safeguard in case the layout forgets to call clear() in its dtor and this dtor is called by QObject dtor mParentLayout->take(this); } /*! Sets the outer rect of this layout element. If the layout element is inside a layout, the layout sets the position and size of this layout element using this function. Calling this function externally has no effect, since the layout will overwrite any changes to the outer rect upon the next replot. The layout element will adapt its inner \ref rect by applying the margins inward to the outer rect. \see rect */ void QCPLayoutElement::setOuterRect(const QRect &rect) { if (mOuterRect != rect) { mOuterRect = rect; mRect = mOuterRect.adjusted(mMargins.left(), mMargins.top(), -mMargins.right(), -mMargins.bottom()); } } /*! Sets the margins of this layout element. If \ref setAutoMargins is disabled for some or all sides, this function is used to manually set the margin on those sides. Sides that are still set to be handled automatically are ignored and may have any value in \a margins. The margin is the distance between the outer rect (controlled by the parent layout via \ref setOuterRect) and the inner \ref rect (which usually contains the main content of this layout element). \see setAutoMargins */ void QCPLayoutElement::setMargins(const QMargins &margins) { if (mMargins != margins) { mMargins = margins; mRect = mOuterRect.adjusted(mMargins.left(), mMargins.top(), -mMargins.right(), -mMargins.bottom()); } } /*! If \ref setAutoMargins is enabled on some or all margins, this function is used to provide minimum values for those margins. The minimum values are not enforced on margin sides that were set to be under manual control via \ref setAutoMargins. \see setAutoMargins */ void QCPLayoutElement::setMinimumMargins(const QMargins &margins) { if (mMinimumMargins != margins) { mMinimumMargins = margins; } } /*! Sets on which sides the margin shall be calculated automatically. If a side is calculated automatically, a minimum margin value may be provided with \ref setMinimumMargins. If a side is set to be controlled manually, the value may be specified with \ref setMargins. Margin sides that are under automatic control may participate in a \ref QCPMarginGroup (see \ref setMarginGroup), to synchronize (align) it with other layout elements in the plot. \see setMinimumMargins, setMargins, QCP::MarginSide */ void QCPLayoutElement::setAutoMargins(QCP::MarginSides sides) { mAutoMargins = sides; } /*! Sets the minimum size for the inner \ref rect of this layout element. A parent layout tries to respect the \a size here by changing row/column sizes in the layout accordingly. If the parent layout size is not sufficient to satisfy all minimum size constraints of its child layout elements, the layout may set a size that is actually smaller than \a size. QCustomPlot propagates the layout's size constraints to the outside by setting its own minimum QWidget size accordingly, so violations of \a size should be exceptions. */ void QCPLayoutElement::setMinimumSize(const QSize &size) { if (mMinimumSize != size) { mMinimumSize = size; if (mParentLayout) mParentLayout->sizeConstraintsChanged(); } } /*! \overload Sets the minimum size for the inner \ref rect of this layout element. */ void QCPLayoutElement::setMinimumSize(int width, int height) { setMinimumSize(QSize(width, height)); } /*! Sets the maximum size for the inner \ref rect of this layout element. A parent layout tries to respect the \a size here by changing row/column sizes in the layout accordingly. */ void QCPLayoutElement::setMaximumSize(const QSize &size) { if (mMaximumSize != size) { mMaximumSize = size; if (mParentLayout) mParentLayout->sizeConstraintsChanged(); } } /*! \overload Sets the maximum size for the inner \ref rect of this layout element. */ void QCPLayoutElement::setMaximumSize(int width, int height) { setMaximumSize(QSize(width, height)); } /*! Sets the margin \a group of the specified margin \a sides. Margin groups allow synchronizing specified margins across layout elements, see the documentation of \ref QCPMarginGroup. To unset the margin group of \a sides, set \a group to 0. Note that margin groups only work for margin sides that are set to automatic (\ref setAutoMargins). \see QCP::MarginSide */ void QCPLayoutElement::setMarginGroup(QCP::MarginSides sides, QCPMarginGroup *group) { QVector sideVector; if (sides.testFlag(QCP::msLeft)) sideVector.append(QCP::msLeft); if (sides.testFlag(QCP::msRight)) sideVector.append(QCP::msRight); if (sides.testFlag(QCP::msTop)) sideVector.append(QCP::msTop); if (sides.testFlag(QCP::msBottom)) sideVector.append(QCP::msBottom); for (int i = 0; i < sideVector.size(); ++i) { QCP::MarginSide side = sideVector.at(i); if (marginGroup(side) != group) { QCPMarginGroup *oldGroup = marginGroup(side); if (oldGroup) // unregister at old group oldGroup->removeChild(side, this); if (!group) // if setting to 0, remove hash entry. Else set hash entry to new group and register there { mMarginGroups.remove(side); } else // setting to a new group { mMarginGroups[side] = group; group->addChild(side, this); } } } } /*! Updates the layout element and sub-elements. This function is automatically called before every replot by the parent layout element. It is called multiple times, once for every \ref UpdatePhase. The phases are run through in the order of the enum values. For details about what happens at the different phases, see the documentation of \ref UpdatePhase. Layout elements that have child elements should call the \ref update method of their child elements, and pass the current \a phase unchanged. The default implementation executes the automatic margin mechanism in the \ref upMargins phase. Subclasses should make sure to call the base class implementation. */ void QCPLayoutElement::update(UpdatePhase phase) { if (phase == upMargins) { if (mAutoMargins != QCP::msNone) { // set the margins of this layout element according to automatic margin calculation, either directly or via a margin group: QMargins newMargins = mMargins; QList allMarginSides = QList() << QCP::msLeft << QCP::msRight << QCP::msTop << QCP::msBottom; foreach (QCP::MarginSide side, allMarginSides) { if (mAutoMargins.testFlag(side)) // this side's margin shall be calculated automatically { if (mMarginGroups.contains(side)) QCP::setMarginValue( newMargins, side, mMarginGroups[side]->commonMargin( side)); // this side is part of a margin group, so get the margin value from that group else QCP::setMarginValue( newMargins, side, calculateAutoMargin( side)); // this side is not part of a group, so calculate the value directly // apply minimum margin restrictions: if (QCP::getMarginValue(newMargins, side) < QCP::getMarginValue(mMinimumMargins, side)) QCP::setMarginValue(newMargins, side, QCP::getMarginValue(mMinimumMargins, side)); } } setMargins(newMargins); } } } /*! Returns the minimum size this layout element (the inner \ref rect) may be compressed to. if a minimum size (\ref setMinimumSize) was not set manually, parent layouts consult this function to determine the minimum allowed size of this layout element. (A manual minimum size is considered set if it is non-zero.) */ QSize QCPLayoutElement::minimumSizeHint() const { return mMinimumSize; } /*! Returns the maximum size this layout element (the inner \ref rect) may be expanded to. if a maximum size (\ref setMaximumSize) was not set manually, parent layouts consult this function to determine the maximum allowed size of this layout element. (A manual maximum size is considered set if it is smaller than Qt's QWIDGETSIZE_MAX.) */ QSize QCPLayoutElement::maximumSizeHint() const { return mMaximumSize; } /*! Returns a list of all child elements in this layout element. If \a recursive is true, all sub-child elements are included in the list, too. \warning There may be entries with value 0 in the returned list. (For example, QCPLayoutGrid may have empty cells which yield 0 at the respective index.) */ QList QCPLayoutElement::elements(bool recursive) const { Q_UNUSED(recursive) return QList(); } /*! Layout elements are sensitive to events inside their outer rect. If \a pos is within the outer rect, this method returns a value corresponding to 0.99 times the parent plot's selection tolerance. However, layout elements are not selectable by default. So if \a onlySelectable is true, -1.0 is returned. See \ref QCPLayerable::selectTest for a general explanation of this virtual method. QCPLayoutElement subclasses may reimplement this method to provide more specific selection test behaviour. */ double QCPLayoutElement::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if (onlySelectable) return -1; if (QRectF(mOuterRect).contains(pos)) { if (mParentPlot) return mParentPlot->selectionTolerance() * 0.99; else { qDebug() << Q_FUNC_INFO << "parent plot not defined"; return -1; } } else return -1; } /*! \internal propagates the parent plot initialization to all child elements, by calling \ref QCPLayerable::initializeParentPlot on them. */ void QCPLayoutElement::parentPlotInitialized(QCustomPlot *parentPlot) { foreach (QCPLayoutElement *el, elements(false)) { if (!el->parentPlot()) el->initializeParentPlot(parentPlot); } } /*! \internal Returns the margin size for this \a side. It is used if automatic margins is enabled for this \a side (see \ref setAutoMargins). If a minimum margin was set with \ref setMinimumMargins, the returned value will not be smaller than the specified minimum margin. The default implementation just returns the respective manual margin (\ref setMargins) or the minimum margin, whichever is larger. */ int QCPLayoutElement::calculateAutoMargin(QCP::MarginSide side) { return qMax(QCP::getMarginValue(mMargins, side), QCP::getMarginValue(mMinimumMargins, side)); } /*! \internal This virtual method is called when this layout element was moved to a different QCPLayout, or when this layout element has changed its logical position (e.g. row and/or column) within the same QCPLayout. Subclasses may use this to react accordingly. Since this method is called after the completion of the move, you can access the new parent layout via \ref layout(). The default implementation does nothing. */ void QCPLayoutElement::layoutChanged() { } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPLayout //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPLayout \brief The abstract base class for layouts This is an abstract base class for layout elements whose main purpose is to define the position and size of other child layout elements. In most cases, layouts don't draw anything themselves (but there are exceptions to this, e.g. QCPLegend). QCPLayout derives from QCPLayoutElement, and thus can itself be nested in other layouts. QCPLayout introduces a common interface for accessing and manipulating the child elements. Those functions are most notably \ref elementCount, \ref elementAt, \ref takeAt, \ref take, \ref simplify, \ref removeAt, \ref remove and \ref clear. Individual subclasses may add more functions to this interface which are more specialized to the form of the layout. For example, \ref QCPLayoutGrid adds functions that take row and column indices to access cells of the layout grid more conveniently. Since this is an abstract base class, you can't instantiate it directly. Rather use one of its subclasses like QCPLayoutGrid or QCPLayoutInset. For a general introduction to the layout system, see the dedicated documentation page \ref thelayoutsystem "The Layout System". */ /* start documentation of pure virtual functions */ /*! \fn virtual int QCPLayout::elementCount() const = 0 Returns the number of elements/cells in the layout. \see elements, elementAt */ /*! \fn virtual QCPLayoutElement* QCPLayout::elementAt(int index) const = 0 Returns the element in the cell with the given \a index. If \a index is invalid, returns 0. Note that even if \a index is valid, the respective cell may be empty in some layouts (e.g. QCPLayoutGrid), so this function may return 0 in those cases. You may use this function to check whether a cell is empty or not. \see elements, elementCount, takeAt */ /*! \fn virtual QCPLayoutElement* QCPLayout::takeAt(int index) = 0 Removes the element with the given \a index from the layout and returns it. If the \a index is invalid or the cell with that index is empty, returns 0. Note that some layouts don't remove the respective cell right away but leave an empty cell after successful removal of the layout element. To collapse empty cells, use \ref simplify. \see elementAt, take */ /*! \fn virtual bool QCPLayout::take(QCPLayoutElement* element) = 0 Removes the specified \a element from the layout and returns true on success. If the \a element isn't in this layout, returns false. Note that some layouts don't remove the respective cell right away but leave an empty cell after successful removal of the layout element. To collapse empty cells, use \ref simplify. \see takeAt */ /* end documentation of pure virtual functions */ /*! Creates an instance of QCPLayout and sets default values. Note that since QCPLayout is an abstract base class, it can't be instantiated directly. */ QCPLayout::QCPLayout() { } /*! First calls the QCPLayoutElement::update base class implementation to update the margins on this layout. Then calls \ref updateLayout which subclasses reimplement to reposition and resize their cells. Finally, \ref update is called on all child elements. */ void QCPLayout::update(UpdatePhase phase) { QCPLayoutElement::update(phase); // set child element rects according to layout: if (phase == upLayout) updateLayout(); // propagate update call to child elements: const int elCount = elementCount(); for (int i = 0; i < elCount; ++i) { if (QCPLayoutElement *el = elementAt(i)) el->update(phase); } } /* inherits documentation from base class */ QList QCPLayout::elements(bool recursive) const { const int c = elementCount(); QList result; #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0) result.reserve(c); #endif for (int i = 0; i < c; ++i) result.append(elementAt(i)); if (recursive) { for (int i = 0; i < c; ++i) { if (result.at(i)) result << result.at(i)->elements(recursive); } } return result; } /*! Simplifies the layout by collapsing empty cells. The exact behavior depends on subclasses, the default implementation does nothing. Not all layouts need simplification. For example, QCPLayoutInset doesn't use explicit simplification while QCPLayoutGrid does. */ void QCPLayout::simplify() { } /*! Removes and deletes the element at the provided \a index. Returns true on success. If \a index is invalid or points to an empty cell, returns false. This function internally uses \ref takeAt to remove the element from the layout and then deletes the returned element. Note that some layouts don't remove the respective cell right away but leave an empty cell after successful removal of the layout element. To collapse empty cells, use \ref simplify. \see remove, takeAt */ bool QCPLayout::removeAt(int index) { if (QCPLayoutElement *el = takeAt(index)) { delete el; return true; } else return false; } /*! Removes and deletes the provided \a element. Returns true on success. If \a element is not in the layout, returns false. This function internally uses \ref takeAt to remove the element from the layout and then deletes the element. Note that some layouts don't remove the respective cell right away but leave an empty cell after successful removal of the layout element. To collapse empty cells, use \ref simplify. \see removeAt, take */ bool QCPLayout::remove(QCPLayoutElement *element) { if (take(element)) { delete element; return true; } else return false; } /*! Removes and deletes all layout elements in this layout. Finally calls \ref simplify to make sure all empty cells are collapsed. \see remove, removeAt */ void QCPLayout::clear() { for (int i = elementCount() - 1; i >= 0; --i) { if (elementAt(i)) removeAt(i); } simplify(); } /*! Subclasses call this method to report changed (minimum/maximum) size constraints. If the parent of this layout is again a QCPLayout, forwards the call to the parent's \ref sizeConstraintsChanged. If the parent is a QWidget (i.e. is the \ref QCustomPlot::plotLayout of QCustomPlot), calls QWidget::updateGeometry, so if the QCustomPlot widget is inside a Qt QLayout, it may update itself and resize cells accordingly. */ void QCPLayout::sizeConstraintsChanged() const { if (QWidget *w = qobject_cast(parent())) w->updateGeometry(); else if (QCPLayout *l = qobject_cast(parent())) l->sizeConstraintsChanged(); } /*! \internal Subclasses reimplement this method to update the position and sizes of the child elements/cells via calling their \ref QCPLayoutElement::setOuterRect. The default implementation does nothing. The geometry used as a reference is the inner \ref rect of this layout. Child elements should stay within that rect. \ref getSectionSizes may help with the reimplementation of this function. \see update */ void QCPLayout::updateLayout() { } /*! \internal Associates \a el with this layout. This is done by setting the \ref QCPLayoutElement::layout, the \ref QCPLayerable::parentLayerable and the QObject parent to this layout. Further, if \a el didn't previously have a parent plot, calls \ref QCPLayerable::initializeParentPlot on \a el to set the paret plot. This method is used by subclass specific methods that add elements to the layout. Note that this method only changes properties in \a el. The removal from the old layout and the insertion into the new layout must be done additionally. */ void QCPLayout::adoptElement(QCPLayoutElement *el) { if (el) { el->mParentLayout = this; el->setParentLayerable(this); el->setParent(this); if (!el->parentPlot()) el->initializeParentPlot(mParentPlot); el->layoutChanged(); } else qDebug() << Q_FUNC_INFO << "Null element passed"; } /*! \internal Disassociates \a el from this layout. This is done by setting the \ref QCPLayoutElement::layout and the \ref QCPLayerable::parentLayerable to zero. The QObject parent is set to the parent QCustomPlot. This method is used by subclass specific methods that remove elements from the layout (e.g. \ref take or \ref takeAt). Note that this method only changes properties in \a el. The removal from the old layout must be done additionally. */ void QCPLayout::releaseElement(QCPLayoutElement *el) { if (el) { - el->mParentLayout = 0; - el->setParentLayerable(0); + el->mParentLayout = nullptr; + el->setParentLayerable(nullptr); el->setParent(mParentPlot); // Note: Don't initializeParentPlot(0) here, because layout element will stay in same parent plot } else qDebug() << Q_FUNC_INFO << "Null element passed"; } /*! \internal This is a helper function for the implementation of \ref updateLayout in subclasses. It calculates the sizes of one-dimensional sections with provided constraints on maximum section sizes, minimum section sizes, relative stretch factors and the final total size of all sections. The QVector entries refer to the sections. Thus all QVectors must have the same size. \a maxSizes gives the maximum allowed size of each section. If there shall be no maximum size imposed, set all vector values to Qt's QWIDGETSIZE_MAX. \a minSizes gives the minimum allowed size of each section. If there shall be no minimum size imposed, set all vector values to zero. If the \a minSizes entries add up to a value greater than \a totalSize, sections will be scaled smaller than the proposed minimum sizes. (In other words, not exceeding the allowed total size is taken to be more important than not going below minimum section sizes.) \a stretchFactors give the relative proportions of the sections to each other. If all sections shall be scaled equally, set all values equal. If the first section shall be double the size of each individual other section, set the first number of \a stretchFactors to double the value of the other individual values (e.g. {2, 1, 1, 1}). \a totalSize is the value that the final section sizes will add up to. Due to rounding, the actual sum may differ slightly. If you want the section sizes to sum up to exactly that value, you could distribute the remaining difference on the sections. The return value is a QVector containing the section sizes. */ QVector QCPLayout::getSectionSizes(QVector maxSizes, QVector minSizes, QVector stretchFactors, int totalSize) const { if (maxSizes.size() != minSizes.size() || minSizes.size() != stretchFactors.size()) { qDebug() << Q_FUNC_INFO << "Passed vector sizes aren't equal:" << maxSizes << minSizes << stretchFactors; return QVector(); } if (stretchFactors.isEmpty()) return QVector(); int sectionCount = stretchFactors.size(); QVector sectionSizes(sectionCount); // if provided total size is forced smaller than total minimum size, ignore minimum sizes (squeeze sections): int minSizeSum = 0; for (int i = 0; i < sectionCount; ++i) minSizeSum += minSizes.at(i); if (totalSize < minSizeSum) { // new stretch factors are minimum sizes and minimum sizes are set to zero: for (int i = 0; i < sectionCount; ++i) { stretchFactors[i] = minSizes.at(i); minSizes[i] = 0; } } QList minimumLockedSections; QList unfinishedSections; for (int i = 0; i < sectionCount; ++i) unfinishedSections.append(i); double freeSize = totalSize; int outerIterations = 0; while (!unfinishedSections.isEmpty() && outerIterations < sectionCount * 2) // the iteration check ist just a failsafe in case something really strange happens { ++outerIterations; int innerIterations = 0; while (!unfinishedSections.isEmpty() && innerIterations < sectionCount * 2) // the iteration check ist just a failsafe in case something really strange happens { ++innerIterations; // find section that hits its maximum next: int nextId = -1; double nextMax = 1e12; for (int i = 0; i < unfinishedSections.size(); ++i) { int secId = unfinishedSections.at(i); double hitsMaxAt = (maxSizes.at(secId) - sectionSizes.at(secId)) / stretchFactors.at(secId); if (hitsMaxAt < nextMax) { nextMax = hitsMaxAt; nextId = secId; } } // check if that maximum is actually within the bounds of the total size (i.e. can we stretch all remaining sections so far that the found section // actually hits its maximum, without exceeding the total size when we add up all sections) double stretchFactorSum = 0; for (int i = 0; i < unfinishedSections.size(); ++i) stretchFactorSum += stretchFactors.at(unfinishedSections.at(i)); double nextMaxLimit = freeSize / stretchFactorSum; if (nextMax < nextMaxLimit) // next maximum is actually hit, move forward to that point and fix the size of that section { for (int i = 0; i < unfinishedSections.size(); ++i) { sectionSizes[unfinishedSections.at(i)] += nextMax * stretchFactors.at(unfinishedSections.at(i)); // increment all sections freeSize -= nextMax * stretchFactors.at(unfinishedSections.at(i)); } unfinishedSections.removeOne(nextId); // exclude the section that is now at maximum from further changes } else // next maximum isn't hit, just distribute rest of free space on remaining sections { for (int i = 0; i < unfinishedSections.size(); ++i) sectionSizes[unfinishedSections.at(i)] += nextMaxLimit * stretchFactors.at(unfinishedSections.at(i)); // increment all sections unfinishedSections.clear(); } } if (innerIterations == sectionCount * 2) qDebug() << Q_FUNC_INFO << "Exceeded maximum expected inner iteration count, layouting aborted. Input was:" << maxSizes << minSizes << stretchFactors << totalSize; // now check whether the resulting section sizes violate minimum restrictions: bool foundMinimumViolation = false; for (int i = 0; i < sectionSizes.size(); ++i) { if (minimumLockedSections.contains(i)) continue; if (sectionSizes.at(i) < minSizes.at(i)) // section violates minimum { sectionSizes[i] = minSizes.at(i); // set it to minimum foundMinimumViolation = true; // make sure we repeat the whole optimization process minimumLockedSections.append(i); } } if (foundMinimumViolation) { freeSize = totalSize; for (int i = 0; i < sectionCount; ++i) { if (!minimumLockedSections.contains( i)) // only put sections that haven't hit their minimum back into the pool unfinishedSections.append(i); else freeSize -= sectionSizes.at(i); // remove size of minimum locked sections from available space in next round } // reset all section sizes to zero that are in unfinished sections (all others have been set to their minimum): for (int i = 0; i < unfinishedSections.size(); ++i) sectionSizes[unfinishedSections.at(i)] = 0; } } if (outerIterations == sectionCount * 2) qDebug() << Q_FUNC_INFO << "Exceeded maximum expected outer iteration count, layouting aborted. Input was:" << maxSizes << minSizes << stretchFactors << totalSize; QVector result(sectionCount); for (int i = 0; i < sectionCount; ++i) result[i] = qRound(sectionSizes.at(i)); return result; } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPLayoutGrid //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPLayoutGrid \brief A layout that arranges child elements in a grid Elements are laid out in a grid with configurable stretch factors (\ref setColumnStretchFactor, \ref setRowStretchFactor) and spacing (\ref setColumnSpacing, \ref setRowSpacing). Elements can be added to cells via \ref addElement. The grid is expanded if the specified row or column doesn't exist yet. Whether a cell contains a valid layout element can be checked with \ref hasElement, that element can be retrieved with \ref element. If rows and columns that only have empty cells shall be removed, call \ref simplify. Removal of elements is either done by just adding the element to a different layout or by using the QCPLayout interface \ref take or \ref remove. If you use \ref addElement(QCPLayoutElement*) without explicit parameters for \a row and \a column, the grid layout will choose the position according to the current \ref setFillOrder and the wrapping (\ref setWrap). Row and column insertion can be performed with \ref insertRow and \ref insertColumn. */ /* start documentation of inline functions */ /*! \fn int QCPLayoutGrid::rowCount() const Returns the number of rows in the layout. \see columnCount */ /*! \fn int QCPLayoutGrid::columnCount() const Returns the number of columns in the layout. \see rowCount */ /* end documentation of inline functions */ /*! Creates an instance of QCPLayoutGrid and sets default values. */ QCPLayoutGrid::QCPLayoutGrid() : mColumnSpacing(5), mRowSpacing(5), mWrap(0), mFillOrder(foRowsFirst) { } QCPLayoutGrid::~QCPLayoutGrid() { // clear all child layout elements. This is important because only the specific layouts know how // to handle removing elements (clear calls virtual removeAt method to do that). clear(); } /*! Returns the element in the cell in \a row and \a column. Returns 0 if either the row/column is invalid or if the cell is empty. In those cases, a qDebug message is printed. To check whether a cell exists and isn't empty, use \ref hasElement. \see addElement, hasElement */ QCPLayoutElement *QCPLayoutGrid::element(int row, int column) const { if (row >= 0 && row < mElements.size()) { if (column >= 0 && column < mElements.first().size()) { if (QCPLayoutElement *result = mElements.at(row).at(column)) return result; else qDebug() << Q_FUNC_INFO << "Requested cell is empty. Row:" << row << "Column:" << column; } else qDebug() << Q_FUNC_INFO << "Invalid column. Row:" << row << "Column:" << column; } else qDebug() << Q_FUNC_INFO << "Invalid row. Row:" << row << "Column:" << column; - return 0; + return nullptr; } /*! \overload Adds the \a element to cell with \a row and \a column. If \a element is already in a layout, it is first removed from there. If \a row or \a column don't exist yet, the layout is expanded accordingly. Returns true if the element was added successfully, i.e. if the cell at \a row and \a column didn't already have an element. Use the overload of this method without explicit row/column index to place the element according to the configured fill order and wrapping settings. \see element, hasElement, take, remove */ bool QCPLayoutGrid::addElement(int row, int column, QCPLayoutElement *element) { if (!hasElement(row, column)) { if (element && element->layout()) // remove from old layout first element->layout()->take(element); expandTo(row + 1, column + 1); mElements[row][column] = element; if (element) adoptElement(element); return true; } else qDebug() << Q_FUNC_INFO << "There is already an element in the specified row/column:" << row << column; return false; } /*! \overload Adds the \a element to the next empty cell according to the current fill order (\ref setFillOrder) and wrapping (\ref setWrap). If \a element is already in a layout, it is first removed from there. If necessary, the layout is expanded to hold the new element. Returns true if the element was added successfully. \see setFillOrder, setWrap, element, hasElement, take, remove */ bool QCPLayoutGrid::addElement(QCPLayoutElement *element) { int rowIndex = 0; int colIndex = 0; if (mFillOrder == foColumnsFirst) { while (hasElement(rowIndex, colIndex)) { ++colIndex; if (colIndex >= mWrap && mWrap > 0) { colIndex = 0; ++rowIndex; } } } else { while (hasElement(rowIndex, colIndex)) { ++rowIndex; if (rowIndex >= mWrap && mWrap > 0) { rowIndex = 0; ++colIndex; } } } return addElement(rowIndex, colIndex, element); } /*! Returns whether the cell at \a row and \a column exists and contains a valid element, i.e. isn't empty. \see element */ bool QCPLayoutGrid::hasElement(int row, int column) { if (row >= 0 && row < rowCount() && column >= 0 && column < columnCount()) return mElements.at(row).at(column); else return false; } /*! Sets the stretch \a factor of \a column. Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond their minimum and maximum widths/heights (\ref QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize), regardless of the stretch factor. The default stretch factor of newly created rows/columns is 1. \see setColumnStretchFactors, setRowStretchFactor */ void QCPLayoutGrid::setColumnStretchFactor(int column, double factor) { if (column >= 0 && column < columnCount()) { if (factor > 0) mColumnStretchFactors[column] = factor; else qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << factor; } else qDebug() << Q_FUNC_INFO << "Invalid column:" << column; } /*! Sets the stretch \a factors of all columns. \a factors must have the size \ref columnCount. Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond their minimum and maximum widths/heights (\ref QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize), regardless of the stretch factor. The default stretch factor of newly created rows/columns is 1. \see setColumnStretchFactor, setRowStretchFactors */ void QCPLayoutGrid::setColumnStretchFactors(const QList &factors) { if (factors.size() == mColumnStretchFactors.size()) { mColumnStretchFactors = factors; for (int i = 0; i < mColumnStretchFactors.size(); ++i) { if (mColumnStretchFactors.at(i) <= 0) { qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << mColumnStretchFactors.at(i); mColumnStretchFactors[i] = 1; } } } else qDebug() << Q_FUNC_INFO << "Column count not equal to passed stretch factor count:" << factors; } /*! Sets the stretch \a factor of \a row. Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond their minimum and maximum widths/heights (\ref QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize), regardless of the stretch factor. The default stretch factor of newly created rows/columns is 1. \see setColumnStretchFactors, setRowStretchFactor */ void QCPLayoutGrid::setRowStretchFactor(int row, double factor) { if (row >= 0 && row < rowCount()) { if (factor > 0) mRowStretchFactors[row] = factor; else qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << factor; } else qDebug() << Q_FUNC_INFO << "Invalid row:" << row; } /*! Sets the stretch \a factors of all rows. \a factors must have the size \ref rowCount. Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond their minimum and maximum widths/heights (\ref QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize), regardless of the stretch factor. The default stretch factor of newly created rows/columns is 1. \see setRowStretchFactor, setColumnStretchFactors */ void QCPLayoutGrid::setRowStretchFactors(const QList &factors) { if (factors.size() == mRowStretchFactors.size()) { mRowStretchFactors = factors; for (int i = 0; i < mRowStretchFactors.size(); ++i) { if (mRowStretchFactors.at(i) <= 0) { qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << mRowStretchFactors.at(i); mRowStretchFactors[i] = 1; } } } else qDebug() << Q_FUNC_INFO << "Row count not equal to passed stretch factor count:" << factors; } /*! Sets the gap that is left blank between columns to \a pixels. \see setRowSpacing */ void QCPLayoutGrid::setColumnSpacing(int pixels) { mColumnSpacing = pixels; } /*! Sets the gap that is left blank between rows to \a pixels. \see setColumnSpacing */ void QCPLayoutGrid::setRowSpacing(int pixels) { mRowSpacing = pixels; } /*! Sets the maximum number of columns or rows that are used, before new elements added with \ref addElement(QCPLayoutElement*) will start to fill the next row or column, respectively. It depends on \ref setFillOrder, whether rows or columns are wrapped. If \a count is set to zero, no wrapping will ever occur. If you wish to re-wrap the elements currently in the layout, call \ref setFillOrder with \a rearrange set to true (the actual fill order doesn't need to be changed for the rearranging to be done). Note that the method \ref addElement(int row, int column, QCPLayoutElement *element) with explicitly stated row and column is not subject to wrapping and can place elements even beyond the specified wrapping point. \see setFillOrder */ void QCPLayoutGrid::setWrap(int count) { mWrap = qMax(0, count); } /*! Sets the filling order and wrapping behaviour that is used when adding new elements with the method \ref addElement(QCPLayoutElement*). The specified \a order defines whether rows or columns are filled first. Using \ref setWrap, you can control at which row/column count wrapping into the next column/row will occur. If you set it to zero, no wrapping will ever occur. Changing the fill order also changes the meaning of the linear index used e.g. in \ref elementAt and \ref takeAt. If you want to have all current elements arranged in the new order, set \a rearrange to true. The elements will be rearranged in a way that tries to preserve their linear index. However, empty cells are skipped during build-up of the new cell order, which shifts the succeding element's index. The rearranging is performed even if the specified \a order is already the current fill order. Thus this method can be used to re-wrap the current elements. If \a rearrange is false, the current element arrangement is not changed, which means the linear indexes change (because the linear index is dependent on the fill order). Note that the method \ref addElement(int row, int column, QCPLayoutElement *element) with explicitly stated row and column is not subject to wrapping and can place elements even beyond the specified wrapping point. \see setWrap, addElement(QCPLayoutElement*) */ void QCPLayoutGrid::setFillOrder(FillOrder order, bool rearrange) { // if rearranging, take all elements via linear index of old fill order: const int elCount = elementCount(); QVector tempElements; if (rearrange) { tempElements.reserve(elCount); for (int i = 0; i < elCount; ++i) { if (elementAt(i)) tempElements.append(takeAt(i)); } simplify(); } // change fill order as requested: mFillOrder = order; // if rearranging, re-insert via linear index according to new fill order: if (rearrange) { for (int i = 0; i < tempElements.size(); ++i) addElement(tempElements.at(i)); } } /*! Expands the layout to have \a newRowCount rows and \a newColumnCount columns. So the last valid row index will be \a newRowCount-1, the last valid column index will be \a newColumnCount-1. If the current column/row count is already larger or equal to \a newColumnCount/\a newRowCount, this function does nothing in that dimension. Newly created cells are empty, new rows and columns have the stretch factor 1. Note that upon a call to \ref addElement, the layout is expanded automatically to contain the specified row and column, using this function. \see simplify */ void QCPLayoutGrid::expandTo(int newRowCount, int newColumnCount) { // add rows as necessary: while (rowCount() < newRowCount) { mElements.append(QList()); mRowStretchFactors.append(1); } // go through rows and expand columns as necessary: int newColCount = qMax(columnCount(), newColumnCount); for (int i = 0; i < rowCount(); ++i) { while (mElements.at(i).size() < newColCount) mElements[i].append(0); } while (mColumnStretchFactors.size() < newColCount) mColumnStretchFactors.append(1); } /*! Inserts a new row with empty cells at the row index \a newIndex. Valid values for \a newIndex range from 0 (inserts a row at the top) to \a rowCount (appends a row at the bottom). \see insertColumn */ void QCPLayoutGrid::insertRow(int newIndex) { if (mElements.isEmpty() || mElements.first().isEmpty()) // if grid is completely empty, add first cell { expandTo(1, 1); return; } if (newIndex < 0) newIndex = 0; if (newIndex > rowCount()) newIndex = rowCount(); mRowStretchFactors.insert(newIndex, 1); QList newRow; for (int col = 0; col < columnCount(); ++col) - newRow.append((QCPLayoutElement *)0); + newRow.append((QCPLayoutElement *)nullptr); mElements.insert(newIndex, newRow); } /*! Inserts a new column with empty cells at the column index \a newIndex. Valid values for \a newIndex range from 0 (inserts a row at the left) to \a rowCount (appends a row at the right). \see insertRow */ void QCPLayoutGrid::insertColumn(int newIndex) { if (mElements.isEmpty() || mElements.first().isEmpty()) // if grid is completely empty, add first cell { expandTo(1, 1); return; } if (newIndex < 0) newIndex = 0; if (newIndex > columnCount()) newIndex = columnCount(); mColumnStretchFactors.insert(newIndex, 1); for (int row = 0; row < rowCount(); ++row) - mElements[row].insert(newIndex, (QCPLayoutElement *)0); + mElements[row].insert(newIndex, (QCPLayoutElement *)nullptr); } /*! Converts the given \a row and \a column to the linear index used by some methods of \ref QCPLayoutGrid and \ref QCPLayout. The way the cells are indexed depends on \ref setFillOrder. If it is \ref foRowsFirst, the indices increase left to right and then top to bottom. If it is \ref foColumnsFirst, the indices increase top to bottom and then left to right. For the returned index to be valid, \a row and \a column must be valid indices themselves, i.e. greater or equal to zero and smaller than the current \ref rowCount/\ref columnCount. \see indexToRowCol */ int QCPLayoutGrid::rowColToIndex(int row, int column) const { if (row >= 0 && row < rowCount()) { if (column >= 0 && column < columnCount()) { switch (mFillOrder) { case foRowsFirst: return column * rowCount() + row; case foColumnsFirst: return row * columnCount() + column; } } else qDebug() << Q_FUNC_INFO << "row index out of bounds:" << row; } else qDebug() << Q_FUNC_INFO << "column index out of bounds:" << column; return 0; } /*! Converts the linear index to row and column indices and writes the result to \a row and \a column. The way the cells are indexed depends on \ref setFillOrder. If it is \ref foRowsFirst, the indices increase left to right and then top to bottom. If it is \ref foColumnsFirst, the indices increase top to bottom and then left to right. If there are no cells (i.e. column or row count is zero), sets \a row and \a column to -1. For the retrieved \a row and \a column to be valid, the passed \a index must be valid itself, i.e. greater or equal to zero and smaller than the current \ref elementCount. \see rowColToIndex */ void QCPLayoutGrid::indexToRowCol(int index, int &row, int &column) const { row = -1; column = -1; if (columnCount() == 0 || rowCount() == 0) return; if (index < 0 || index >= elementCount()) { qDebug() << Q_FUNC_INFO << "index out of bounds:" << index; return; } switch (mFillOrder) { case foRowsFirst: { column = index / rowCount(); row = index % rowCount(); break; } case foColumnsFirst: { row = index / columnCount(); column = index % columnCount(); break; } } } /* inherits documentation from base class */ void QCPLayoutGrid::updateLayout() { QVector minColWidths, minRowHeights, maxColWidths, maxRowHeights; getMinimumRowColSizes(&minColWidths, &minRowHeights); getMaximumRowColSizes(&maxColWidths, &maxRowHeights); int totalRowSpacing = (rowCount() - 1) * mRowSpacing; int totalColSpacing = (columnCount() - 1) * mColumnSpacing; QVector colWidths = getSectionSizes(maxColWidths, minColWidths, mColumnStretchFactors.toVector(), mRect.width() - totalColSpacing); QVector rowHeights = getSectionSizes(maxRowHeights, minRowHeights, mRowStretchFactors.toVector(), mRect.height() - totalRowSpacing); // go through cells and set rects accordingly: int yOffset = mRect.top(); for (int row = 0; row < rowCount(); ++row) { if (row > 0) yOffset += rowHeights.at(row - 1) + mRowSpacing; int xOffset = mRect.left(); for (int col = 0; col < columnCount(); ++col) { if (col > 0) xOffset += colWidths.at(col - 1) + mColumnSpacing; if (mElements.at(row).at(col)) mElements.at(row).at(col)->setOuterRect(QRect(xOffset, yOffset, colWidths.at(col), rowHeights.at(row))); } } } /*! \seebaseclassmethod Note that the association of the linear \a index to the row/column based cells depends on the current setting of \ref setFillOrder. \see rowColToIndex */ QCPLayoutElement *QCPLayoutGrid::elementAt(int index) const { if (index >= 0 && index < elementCount()) { int row, col; indexToRowCol(index, row, col); return mElements.at(row).at(col); } else - return 0; + return nullptr; } /*! \seebaseclassmethod Note that the association of the linear \a index to the row/column based cells depends on the current setting of \ref setFillOrder. \see rowColToIndex */ QCPLayoutElement *QCPLayoutGrid::takeAt(int index) { if (QCPLayoutElement *el = elementAt(index)) { releaseElement(el); int row, col; indexToRowCol(index, row, col); mElements[row][col] = 0; return el; } else { qDebug() << Q_FUNC_INFO << "Attempt to take invalid index:" << index; - return 0; + return nullptr; } } /* inherits documentation from base class */ bool QCPLayoutGrid::take(QCPLayoutElement *element) { if (element) { for (int i = 0; i < elementCount(); ++i) { if (elementAt(i) == element) { takeAt(i); return true; } } qDebug() << Q_FUNC_INFO << "Element not in this layout, couldn't take"; } else qDebug() << Q_FUNC_INFO << "Can't take null element"; return false; } /* inherits documentation from base class */ QList QCPLayoutGrid::elements(bool recursive) const { QList result; const int elCount = elementCount(); #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0) result.reserve(elCount); #endif for (int i = 0; i < elCount; ++i) result.append(elementAt(i)); if (recursive) { for (int i = 0; i < elCount; ++i) { if (result.at(i)) result << result.at(i)->elements(recursive); } } return result; } /*! Simplifies the layout by collapsing rows and columns which only contain empty cells. */ void QCPLayoutGrid::simplify() { // remove rows with only empty cells: for (int row = rowCount() - 1; row >= 0; --row) { bool hasElements = false; for (int col = 0; col < columnCount(); ++col) { if (mElements.at(row).at(col)) { hasElements = true; break; } } if (!hasElements) { mRowStretchFactors.removeAt(row); mElements.removeAt(row); if (mElements .isEmpty()) // removed last element, also remove stretch factor (wouldn't happen below because also columnCount changed to 0 now) mColumnStretchFactors.clear(); } } // remove columns with only empty cells: for (int col = columnCount() - 1; col >= 0; --col) { bool hasElements = false; for (int row = 0; row < rowCount(); ++row) { if (mElements.at(row).at(col)) { hasElements = true; break; } } if (!hasElements) { mColumnStretchFactors.removeAt(col); for (int row = 0; row < rowCount(); ++row) mElements[row].removeAt(col); } } } /* inherits documentation from base class */ QSize QCPLayoutGrid::minimumSizeHint() const { QVector minColWidths, minRowHeights; getMinimumRowColSizes(&minColWidths, &minRowHeights); QSize result(0, 0); for (int i = 0; i < minColWidths.size(); ++i) result.rwidth() += minColWidths.at(i); for (int i = 0; i < minRowHeights.size(); ++i) result.rheight() += minRowHeights.at(i); result.rwidth() += qMax(0, columnCount() - 1) * mColumnSpacing + mMargins.left() + mMargins.right(); result.rheight() += qMax(0, rowCount() - 1) * mRowSpacing + mMargins.top() + mMargins.bottom(); return result; } /* inherits documentation from base class */ QSize QCPLayoutGrid::maximumSizeHint() const { QVector maxColWidths, maxRowHeights; getMaximumRowColSizes(&maxColWidths, &maxRowHeights); QSize result(0, 0); for (int i = 0; i < maxColWidths.size(); ++i) result.setWidth(qMin(result.width() + maxColWidths.at(i), QWIDGETSIZE_MAX)); for (int i = 0; i < maxRowHeights.size(); ++i) result.setHeight(qMin(result.height() + maxRowHeights.at(i), QWIDGETSIZE_MAX)); result.rwidth() += qMax(0, columnCount() - 1) * mColumnSpacing + mMargins.left() + mMargins.right(); result.rheight() += qMax(0, rowCount() - 1) * mRowSpacing + mMargins.top() + mMargins.bottom(); return result; } /*! \internal Places the minimum column widths and row heights into \a minColWidths and \a minRowHeights respectively. The minimum height of a row is the largest minimum height of any element in that row. The minimum width of a column is the largest minimum width of any element in that column. This is a helper function for \ref updateLayout. \see getMaximumRowColSizes */ void QCPLayoutGrid::getMinimumRowColSizes(QVector *minColWidths, QVector *minRowHeights) const { *minColWidths = QVector(columnCount(), 0); *minRowHeights = QVector(rowCount(), 0); for (int row = 0; row < rowCount(); ++row) { for (int col = 0; col < columnCount(); ++col) { if (mElements.at(row).at(col)) { QSize minHint = mElements.at(row).at(col)->minimumSizeHint(); QSize min = mElements.at(row).at(col)->minimumSize(); QSize final(min.width() > 0 ? min.width() : minHint.width(), min.height() > 0 ? min.height() : minHint.height()); if (minColWidths->at(col) < final.width()) (*minColWidths)[col] = final.width(); if (minRowHeights->at(row) < final.height()) (*minRowHeights)[row] = final.height(); } } } } /*! \internal Places the maximum column widths and row heights into \a maxColWidths and \a maxRowHeights respectively. The maximum height of a row is the smallest maximum height of any element in that row. The maximum width of a column is the smallest maximum width of any element in that column. This is a helper function for \ref updateLayout. \see getMinimumRowColSizes */ void QCPLayoutGrid::getMaximumRowColSizes(QVector *maxColWidths, QVector *maxRowHeights) const { *maxColWidths = QVector(columnCount(), QWIDGETSIZE_MAX); *maxRowHeights = QVector(rowCount(), QWIDGETSIZE_MAX); for (int row = 0; row < rowCount(); ++row) { for (int col = 0; col < columnCount(); ++col) { if (mElements.at(row).at(col)) { QSize maxHint = mElements.at(row).at(col)->maximumSizeHint(); QSize max = mElements.at(row).at(col)->maximumSize(); QSize final(max.width() < QWIDGETSIZE_MAX ? max.width() : maxHint.width(), max.height() < QWIDGETSIZE_MAX ? max.height() : maxHint.height()); if (maxColWidths->at(col) > final.width()) (*maxColWidths)[col] = final.width(); if (maxRowHeights->at(row) > final.height()) (*maxRowHeights)[row] = final.height(); } } } } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPLayoutInset //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPLayoutInset \brief A layout that places child elements aligned to the border or arbitrarily positioned Elements are placed either aligned to the border or at arbitrary position in the area of the layout. Which placement applies is controlled with the \ref InsetPlacement (\ref setInsetPlacement). Elements are added via \ref addElement(QCPLayoutElement *element, Qt::Alignment alignment) or addElement(QCPLayoutElement *element, const QRectF &rect). If the first method is used, the inset placement will default to \ref ipBorderAligned and the element will be aligned according to the \a alignment parameter. The second method defaults to \ref ipFree and allows placing elements at arbitrary position and size, defined by \a rect. The alignment or rect can be set via \ref setInsetAlignment or \ref setInsetRect, respectively. This is the layout that every QCPAxisRect has as \ref QCPAxisRect::insetLayout. */ /* start documentation of inline functions */ /*! \fn virtual void QCPLayoutInset::simplify() The QCPInsetLayout does not need simplification since it can never have empty cells due to its linear index structure. This method does nothing. */ /* end documentation of inline functions */ /*! Creates an instance of QCPLayoutInset and sets default values. */ QCPLayoutInset::QCPLayoutInset() { } QCPLayoutInset::~QCPLayoutInset() { // clear all child layout elements. This is important because only the specific layouts know how // to handle removing elements (clear calls virtual removeAt method to do that). clear(); } /*! Returns the placement type of the element with the specified \a index. */ QCPLayoutInset::InsetPlacement QCPLayoutInset::insetPlacement(int index) const { if (elementAt(index)) return mInsetPlacement.at(index); else { qDebug() << Q_FUNC_INFO << "Invalid element index:" << index; return ipFree; } } /*! Returns the alignment of the element with the specified \a index. The alignment only has a meaning, if the inset placement (\ref setInsetPlacement) is \ref ipBorderAligned. */ Qt::Alignment QCPLayoutInset::insetAlignment(int index) const { if (elementAt(index)) return mInsetAlignment.at(index); else { qDebug() << Q_FUNC_INFO << "Invalid element index:" << index; - return 0; + return nullptr; } } /*! Returns the rect of the element with the specified \a index. The rect only has a meaning, if the inset placement (\ref setInsetPlacement) is \ref ipFree. */ QRectF QCPLayoutInset::insetRect(int index) const { if (elementAt(index)) return mInsetRect.at(index); else { qDebug() << Q_FUNC_INFO << "Invalid element index:" << index; return QRectF(); } } /*! Sets the inset placement type of the element with the specified \a index to \a placement. \see InsetPlacement */ void QCPLayoutInset::setInsetPlacement(int index, QCPLayoutInset::InsetPlacement placement) { if (elementAt(index)) mInsetPlacement[index] = placement; else qDebug() << Q_FUNC_INFO << "Invalid element index:" << index; } /*! If the inset placement (\ref setInsetPlacement) is \ref ipBorderAligned, this function is used to set the alignment of the element with the specified \a index to \a alignment. \a alignment is an or combination of the following alignment flags: Qt::AlignLeft, Qt::AlignHCenter, Qt::AlighRight, Qt::AlignTop, Qt::AlignVCenter, Qt::AlignBottom. Any other alignment flags will be ignored. */ void QCPLayoutInset::setInsetAlignment(int index, Qt::Alignment alignment) { if (elementAt(index)) mInsetAlignment[index] = alignment; else qDebug() << Q_FUNC_INFO << "Invalid element index:" << index; } /*! If the inset placement (\ref setInsetPlacement) is \ref ipFree, this function is used to set the position and size of the element with the specified \a index to \a rect. \a rect is given in fractions of the whole inset layout rect. So an inset with rect (0, 0, 1, 1) will span the entire layout. An inset with rect (0.6, 0.1, 0.35, 0.35) will be in the top right corner of the layout, with 35% width and height of the parent layout. Note that the minimum and maximum sizes of the embedded element (\ref QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize) are enforced. */ void QCPLayoutInset::setInsetRect(int index, const QRectF &rect) { if (elementAt(index)) mInsetRect[index] = rect; else qDebug() << Q_FUNC_INFO << "Invalid element index:" << index; } /* inherits documentation from base class */ void QCPLayoutInset::updateLayout() { for (int i = 0; i < mElements.size(); ++i) { QRect insetRect; QSize finalMinSize, finalMaxSize; QSize minSizeHint = mElements.at(i)->minimumSizeHint(); QSize maxSizeHint = mElements.at(i)->maximumSizeHint(); finalMinSize.setWidth(mElements.at(i)->minimumSize().width() > 0 ? mElements.at(i)->minimumSize().width() : minSizeHint.width()); finalMinSize.setHeight(mElements.at(i)->minimumSize().height() > 0 ? mElements.at(i)->minimumSize().height() : minSizeHint.height()); finalMaxSize.setWidth(mElements.at(i)->maximumSize().width() < QWIDGETSIZE_MAX ? mElements.at(i)->maximumSize().width() : maxSizeHint.width()); finalMaxSize.setHeight(mElements.at(i)->maximumSize().height() < QWIDGETSIZE_MAX ? mElements.at(i)->maximumSize().height() : maxSizeHint.height()); if (mInsetPlacement.at(i) == ipFree) { insetRect = QRect(rect().x() + rect().width() * mInsetRect.at(i).x(), rect().y() + rect().height() * mInsetRect.at(i).y(), rect().width() * mInsetRect.at(i).width(), rect().height() * mInsetRect.at(i).height()); if (insetRect.size().width() < finalMinSize.width()) insetRect.setWidth(finalMinSize.width()); if (insetRect.size().height() < finalMinSize.height()) insetRect.setHeight(finalMinSize.height()); if (insetRect.size().width() > finalMaxSize.width()) insetRect.setWidth(finalMaxSize.width()); if (insetRect.size().height() > finalMaxSize.height()) insetRect.setHeight(finalMaxSize.height()); } else if (mInsetPlacement.at(i) == ipBorderAligned) { insetRect.setSize(finalMinSize); Qt::Alignment al = mInsetAlignment.at(i); if (al.testFlag(Qt::AlignLeft)) insetRect.moveLeft(rect().x()); else if (al.testFlag(Qt::AlignRight)) insetRect.moveRight(rect().x() + rect().width()); else insetRect.moveLeft(rect().x() + rect().width() * 0.5 - finalMinSize.width() * 0.5); // default to Qt::AlignHCenter if (al.testFlag(Qt::AlignTop)) insetRect.moveTop(rect().y()); else if (al.testFlag(Qt::AlignBottom)) insetRect.moveBottom(rect().y() + rect().height()); else insetRect.moveTop(rect().y() + rect().height() * 0.5 - finalMinSize.height() * 0.5); // default to Qt::AlignVCenter } mElements.at(i)->setOuterRect(insetRect); } } /* inherits documentation from base class */ int QCPLayoutInset::elementCount() const { return mElements.size(); } /* inherits documentation from base class */ QCPLayoutElement *QCPLayoutInset::elementAt(int index) const { if (index >= 0 && index < mElements.size()) return mElements.at(index); else - return 0; + return nullptr; } /* inherits documentation from base class */ QCPLayoutElement *QCPLayoutInset::takeAt(int index) { if (QCPLayoutElement *el = elementAt(index)) { releaseElement(el); mElements.removeAt(index); mInsetPlacement.removeAt(index); mInsetAlignment.removeAt(index); mInsetRect.removeAt(index); return el; } else { qDebug() << Q_FUNC_INFO << "Attempt to take invalid index:" << index; - return 0; + return nullptr; } } /* inherits documentation from base class */ bool QCPLayoutInset::take(QCPLayoutElement *element) { if (element) { for (int i = 0; i < elementCount(); ++i) { if (elementAt(i) == element) { takeAt(i); return true; } } qDebug() << Q_FUNC_INFO << "Element not in this layout, couldn't take"; } else qDebug() << Q_FUNC_INFO << "Can't take null element"; return false; } /*! The inset layout is sensitive to events only at areas where its (visible) child elements are sensitive. If the selectTest method of any of the child elements returns a positive number for \a pos, this method returns a value corresponding to 0.99 times the parent plot's selection tolerance. The inset layout is not selectable itself by default. So if \a onlySelectable is true, -1.0 is returned. See \ref QCPLayerable::selectTest for a general explanation of this virtual method. */ double QCPLayoutInset::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if (onlySelectable) return -1; for (int i = 0; i < mElements.size(); ++i) { // inset layout shall only return positive selectTest, if actually an inset object is at pos // else it would block the entire underlying QCPAxisRect with its surface. if (mElements.at(i)->realVisibility() && mElements.at(i)->selectTest(pos, onlySelectable) >= 0) return mParentPlot->selectionTolerance() * 0.99; } return -1; } /*! Adds the specified \a element to the layout as an inset aligned at the border (\ref setInsetAlignment is initialized with \ref ipBorderAligned). The alignment is set to \a alignment. \a alignment is an or combination of the following alignment flags: Qt::AlignLeft, Qt::AlignHCenter, Qt::AlighRight, Qt::AlignTop, Qt::AlignVCenter, Qt::AlignBottom. Any other alignment flags will be ignored. \see addElement(QCPLayoutElement *element, const QRectF &rect) */ void QCPLayoutInset::addElement(QCPLayoutElement *element, Qt::Alignment alignment) { if (element) { if (element->layout()) // remove from old layout first element->layout()->take(element); mElements.append(element); mInsetPlacement.append(ipBorderAligned); mInsetAlignment.append(alignment); mInsetRect.append(QRectF(0.6, 0.6, 0.4, 0.4)); adoptElement(element); } else qDebug() << Q_FUNC_INFO << "Can't add null element"; } /*! Adds the specified \a element to the layout as an inset with free positioning/sizing (\ref setInsetAlignment is initialized with \ref ipFree). The position and size is set to \a rect. \a rect is given in fractions of the whole inset layout rect. So an inset with rect (0, 0, 1, 1) will span the entire layout. An inset with rect (0.6, 0.1, 0.35, 0.35) will be in the top right corner of the layout, with 35% width and height of the parent layout. \see addElement(QCPLayoutElement *element, Qt::Alignment alignment) */ void QCPLayoutInset::addElement(QCPLayoutElement *element, const QRectF &rect) { if (element) { if (element->layout()) // remove from old layout first element->layout()->take(element); mElements.append(element); mInsetPlacement.append(ipFree); mInsetAlignment.append(Qt::AlignRight | Qt::AlignTop); mInsetRect.append(rect); adoptElement(element); } else qDebug() << Q_FUNC_INFO << "Can't add null element"; } /* end of 'src/layout.cpp' */ /* including file 'src/lineending.cpp', size 11536 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPLineEnding //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPLineEnding \brief Handles the different ending decorations for line-like items \image html QCPLineEnding.png "The various ending styles currently supported" For every ending a line-like item has, an instance of this class exists. For example, QCPItemLine has two endings which can be set with QCPItemLine::setHead and QCPItemLine::setTail. The styles themselves are defined via the enum QCPLineEnding::EndingStyle. Most decorations can be modified regarding width and length, see \ref setWidth and \ref setLength. The direction of the ending decoration (e.g. direction an arrow is pointing) is controlled by the line-like item. For example, when both endings of a QCPItemLine are set to be arrows, they will point to opposite directions, e.g. "outward". This can be changed by \ref setInverted, which would make the respective arrow point inward. Note that due to the overloaded QCPLineEnding constructor, you may directly specify a QCPLineEnding::EndingStyle where actually a QCPLineEnding is expected, e.g. \snippet documentation/doc-code-snippets/mainwindow.cpp qcplineending-sethead */ /*! Creates a QCPLineEnding instance with default values (style \ref esNone). */ QCPLineEnding::QCPLineEnding() : mStyle(esNone), mWidth(8), mLength(10), mInverted(false) { } /*! Creates a QCPLineEnding instance with the specified values. */ QCPLineEnding::QCPLineEnding(QCPLineEnding::EndingStyle style, double width, double length, bool inverted) : mStyle(style), mWidth(width), mLength(length), mInverted(inverted) { } /*! Sets the style of the ending decoration. */ void QCPLineEnding::setStyle(QCPLineEnding::EndingStyle style) { mStyle = style; } /*! Sets the width of the ending decoration, if the style supports it. On arrows, for example, the width defines the size perpendicular to the arrow's pointing direction. \see setLength */ void QCPLineEnding::setWidth(double width) { mWidth = width; } /*! Sets the length of the ending decoration, if the style supports it. On arrows, for example, the length defines the size in pointing direction. \see setWidth */ void QCPLineEnding::setLength(double length) { mLength = length; } /*! Sets whether the ending decoration shall be inverted. For example, an arrow decoration will point inward when \a inverted is set to true. Note that also the \a width direction is inverted. For symmetrical ending styles like arrows or discs, this doesn't make a difference. However, asymmetric styles like \ref esHalfBar are affected by it, which can be used to control to which side the half bar points to. */ void QCPLineEnding::setInverted(bool inverted) { mInverted = inverted; } /*! \internal Returns the maximum pixel radius the ending decoration might cover, starting from the position the decoration is drawn at (typically a line ending/\ref QCPItemPosition of an item). This is relevant for clipping. Only omit painting of the decoration when the position where the decoration is supposed to be drawn is farther away from the clipping rect than the returned distance. */ double QCPLineEnding::boundingDistance() const { switch (mStyle) { case esNone: return 0; case esFlatArrow: case esSpikeArrow: case esLineArrow: case esSkewedBar: return qSqrt(mWidth * mWidth + mLength * mLength); // items that have width and length case esDisc: case esSquare: case esDiamond: case esBar: case esHalfBar: return mWidth * 1.42; // items that only have a width -> width*sqrt(2) } return 0; } /*! Starting from the origin of this line ending (which is style specific), returns the length covered by the line ending symbol, in backward direction. For example, the \ref esSpikeArrow has a shorter real length than a \ref esFlatArrow, even if both have the same \ref setLength value, because the spike arrow has an inward curved back, which reduces the length along its center axis (the drawing origin for arrows is at the tip). This function is used for precise, style specific placement of line endings, for example in QCPAxes. */ double QCPLineEnding::realLength() const { switch (mStyle) { case esNone: case esLineArrow: case esSkewedBar: case esBar: case esHalfBar: return 0; case esFlatArrow: return mLength; case esDisc: case esSquare: case esDiamond: return mWidth * 0.5; case esSpikeArrow: return mLength * 0.8; } return 0; } /*! \internal Draws the line ending with the specified \a painter at the position \a pos. The direction of the line ending is controlled with \a dir. */ void QCPLineEnding::draw(QCPPainter *painter, const QCPVector2D &pos, const QCPVector2D &dir) const { if (mStyle == esNone) return; QCPVector2D lengthVec = dir.normalized() * mLength * (mInverted ? -1 : 1); if (lengthVec.isNull()) lengthVec = QCPVector2D(1, 0); QCPVector2D widthVec = dir.normalized().perpendicular() * mWidth * 0.5 * (mInverted ? -1 : 1); QPen penBackup = painter->pen(); QBrush brushBackup = painter->brush(); QPen miterPen = penBackup; miterPen.setJoinStyle(Qt::MiterJoin); // to make arrow heads spikey QBrush brush(painter->pen().color(), Qt::SolidPattern); switch (mStyle) { case esNone: break; case esFlatArrow: { QPointF points[3] = { pos.toPointF(), (pos - lengthVec + widthVec).toPointF(), (pos - lengthVec - widthVec).toPointF() }; painter->setPen(miterPen); painter->setBrush(brush); painter->drawConvexPolygon(points, 3); painter->setBrush(brushBackup); painter->setPen(penBackup); break; } case esSpikeArrow: { QPointF points[4] = { pos.toPointF(), (pos - lengthVec + widthVec).toPointF(), (pos - lengthVec * 0.8).toPointF(), (pos - lengthVec - widthVec).toPointF() }; painter->setPen(miterPen); painter->setBrush(brush); painter->drawConvexPolygon(points, 4); painter->setBrush(brushBackup); painter->setPen(penBackup); break; } case esLineArrow: { QPointF points[3] = { (pos - lengthVec + widthVec).toPointF(), pos.toPointF(), (pos - lengthVec - widthVec).toPointF() }; painter->setPen(miterPen); painter->drawPolyline(points, 3); painter->setPen(penBackup); break; } case esDisc: { painter->setBrush(brush); painter->drawEllipse(pos.toPointF(), mWidth * 0.5, mWidth * 0.5); painter->setBrush(brushBackup); break; } case esSquare: { QCPVector2D widthVecPerp = widthVec.perpendicular(); QPointF points[4] = { (pos - widthVecPerp + widthVec).toPointF(), (pos - widthVecPerp - widthVec).toPointF(), (pos + widthVecPerp - widthVec).toPointF(), (pos + widthVecPerp + widthVec).toPointF() }; painter->setPen(miterPen); painter->setBrush(brush); painter->drawConvexPolygon(points, 4); painter->setBrush(brushBackup); painter->setPen(penBackup); break; } case esDiamond: { QCPVector2D widthVecPerp = widthVec.perpendicular(); QPointF points[4] = { (pos - widthVecPerp).toPointF(), (pos - widthVec).toPointF(), (pos + widthVecPerp).toPointF(), (pos + widthVec).toPointF() }; painter->setPen(miterPen); painter->setBrush(brush); painter->drawConvexPolygon(points, 4); painter->setBrush(brushBackup); painter->setPen(penBackup); break; } case esBar: { painter->drawLine((pos + widthVec).toPointF(), (pos - widthVec).toPointF()); break; } case esHalfBar: { painter->drawLine((pos + widthVec).toPointF(), pos.toPointF()); break; } case esSkewedBar: { if (qFuzzyIsNull(painter->pen().widthF()) && !painter->modes().testFlag(QCPPainter::pmNonCosmetic)) { // if drawing with cosmetic pen (perfectly thin stroke, happens only in vector exports), draw bar exactly on tip of line painter->drawLine((pos + widthVec + lengthVec * 0.2 * (mInverted ? -1 : 1)).toPointF(), (pos - widthVec - lengthVec * 0.2 * (mInverted ? -1 : 1)).toPointF()); } else { // if drawing with thick (non-cosmetic) pen, shift bar a little in line direction to prevent line from sticking through bar slightly painter->drawLine((pos + widthVec + lengthVec * 0.2 * (mInverted ? -1 : 1) + dir.normalized() * qMax(1.0f, (float)painter->pen().widthF()) * 0.5f) .toPointF(), (pos - widthVec - lengthVec * 0.2 * (mInverted ? -1 : 1) + dir.normalized() * qMax(1.0f, (float)painter->pen().widthF()) * 0.5f) .toPointF()); } break; } } } /*! \internal \overload Draws the line ending. The direction is controlled with the \a angle parameter in radians. */ void QCPLineEnding::draw(QCPPainter *painter, const QCPVector2D &pos, double angle) const { draw(painter, pos, QCPVector2D(qCos(angle), qSin(angle))); } /* end of 'src/lineending.cpp' */ /* including file 'src/axis/axisticker.cpp', size 18664 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxisTicker //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPAxisTicker \brief The base class tick generator used by QCPAxis to create tick positions and tick labels Each QCPAxis has an internal QCPAxisTicker (or a subclass) in order to generate tick positions and tick labels for the current axis range. The ticker of an axis can be set via \ref QCPAxis::setTicker. Since that method takes a QSharedPointer, multiple axes can share the same ticker instance. This base class generates normal tick coordinates and numeric labels for linear axes. It picks a reasonable tick step (the separation between ticks) which results in readable tick labels. The number of ticks that should be approximately generated can be set via \ref setTickCount. Depending on the current tick step strategy (\ref setTickStepStrategy), the algorithm either sacrifices readability to better match the specified tick count (\ref QCPAxisTicker::tssMeetTickCount) or relaxes the tick count in favor of better tick steps (\ref QCPAxisTicker::tssReadability), which is the default. The following more specialized axis ticker subclasses are available, see details in the respective class documentation:
QCPAxisTickerFixed\image html axisticker-fixed.png
QCPAxisTickerLog\image html axisticker-log.png
QCPAxisTickerPi\image html axisticker-pi.png
QCPAxisTickerText\image html axisticker-text.png
QCPAxisTickerDateTime\image html axisticker-datetime.png
QCPAxisTickerTime\image html axisticker-time.png \image html axisticker-time2.png
\section axisticker-subclassing Creating own axis tickers Creating own axis tickers can be achieved very easily by sublassing QCPAxisTicker and reimplementing some or all of the available virtual methods. In the simplest case you might wish to just generate different tick steps than the other tickers, so you only reimplement the method \ref getTickStep. If you additionally want control over the string that will be shown as tick label, reimplement \ref getTickLabel. If you wish to have complete control, you can generate the tick vectors and tick label vectors yourself by reimplementing \ref createTickVector and \ref createLabelVector. The default implementations use the previously mentioned virtual methods \ref getTickStep and \ref getTickLabel, but your reimplementations don't necessarily need to do so. For example in the case of unequal tick steps, the method \ref getTickStep loses its usefulness and can be ignored. The sub tick count between major ticks can be controlled with \ref getSubTickCount. Full sub tick placement control is obtained by reimplementing \ref createSubTickVector. See the documentation of all these virtual methods in QCPAxisTicker for detailed information about the parameters and expected return values. */ /*! Constructs the ticker and sets reasonable default values. Axis tickers are commonly created managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker. */ QCPAxisTicker::QCPAxisTicker() : mTickStepStrategy(tssReadability), mTickCount(5), mTickOrigin(0) { } QCPAxisTicker::~QCPAxisTicker() { } /*! Sets which strategy the axis ticker follows when choosing the size of the tick step. For the available strategies, see \ref TickStepStrategy. */ void QCPAxisTicker::setTickStepStrategy(QCPAxisTicker::TickStepStrategy strategy) { mTickStepStrategy = strategy; } /*! Sets how many ticks this ticker shall aim to generate across the axis range. Note that \a count is not guaranteed to be matched exactly, as generating readable tick intervals may conflict with the requested number of ticks. Whether the readability has priority over meeting the requested \a count can be specified with \ref setTickStepStrategy. */ void QCPAxisTicker::setTickCount(int count) { if (count > 0) mTickCount = count; else qDebug() << Q_FUNC_INFO << "tick count must be greater than zero:" << count; } /*! Sets the mathematical coordinate (or "offset") of the zeroth tick. This tick coordinate is just a concept and doesn't need to be inside the currently visible axis range. By default \a origin is zero, which for example yields ticks {-5, 0, 5, 10, 15,...} when the tick step is five. If \a origin is now set to 1 instead, the correspondingly generated ticks would be {-4, 1, 6, 11, 16,...}. */ void QCPAxisTicker::setTickOrigin(double origin) { mTickOrigin = origin; } /*! This is the method called by QCPAxis in order to actually generate tick coordinates (\a ticks), tick label strings (\a tickLabels) and sub tick coordinates (\a subTicks). The ticks are generated for the specified \a range. The generated labels typically follow the specified \a locale, \a formatChar and number \a precision, however this might be different (or even irrelevant) for certain QCPAxisTicker subclasses. The output parameter \a ticks is filled with the generated tick positions in axis coordinates. The output parameters \a subTicks and \a tickLabels are optional (set them to 0 if not needed) and are respectively filled with sub tick coordinates, and tick label strings belonging to \a ticks by index. */ void QCPAxisTicker::generate(const QCPRange &range, const QLocale &locale, QChar formatChar, int precision, QVector &ticks, QVector *subTicks, QVector *tickLabels) { // generate (major) ticks: double tickStep = getTickStep(range); ticks = createTickVector(tickStep, range); trimTicks( range, ticks, true); // trim ticks to visible range plus one outer tick on each side (incase a subclass createTickVector creates more) // generate sub ticks between major ticks: if (subTicks) { if (ticks.size() > 0) { *subTicks = createSubTickVector(getSubTickCount(tickStep), ticks); trimTicks(range, *subTicks, false); } else *subTicks = QVector(); } // finally trim also outliers (no further clipping happens in axis drawing): trimTicks(range, ticks, false); // generate labels for visible ticks if requested: if (tickLabels) *tickLabels = createLabelVector(ticks, locale, formatChar, precision); } /*! \internal Takes the entire currently visible axis range and returns a sensible tick step in order to provide readable tick labels as well as a reasonable number of tick counts (see \ref setTickCount, \ref setTickStepStrategy). If a QCPAxisTicker subclass only wants a different tick step behaviour than the default implementation, it should reimplement this method. See \ref cleanMantissa for a possible helper function. */ double QCPAxisTicker::getTickStep(const QCPRange &range) { double exactStep = range.size() / (double)(mTickCount + 1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers return cleanMantissa(exactStep); } /*! \internal Takes the \a tickStep, i.e. the distance between two consecutive ticks, and returns an appropriate number of sub ticks for that specific tick step. Note that a returned sub tick count of e.g. 4 will split each tick interval into 5 sections. */ int QCPAxisTicker::getSubTickCount(double tickStep) { int result = 1; // default to 1, if no proper value can be found // separate integer and fractional part of mantissa: double epsilon = 0.01; double intPartf; int intPart; double fracPart = modf(getMantissa(tickStep), &intPartf); intPart = intPartf; // handle cases with (almost) integer mantissa: if (fracPart < epsilon || 1.0 - fracPart < epsilon) { if (1.0 - fracPart < epsilon) ++intPart; switch (intPart) { case 1: result = 4; break; // 1.0 -> 0.2 substep case 2: result = 3; break; // 2.0 -> 0.5 substep case 3: result = 2; break; // 3.0 -> 1.0 substep case 4: result = 3; break; // 4.0 -> 1.0 substep case 5: result = 4; break; // 5.0 -> 1.0 substep case 6: result = 2; break; // 6.0 -> 2.0 substep case 7: result = 6; break; // 7.0 -> 1.0 substep case 8: result = 3; break; // 8.0 -> 2.0 substep case 9: result = 2; break; // 9.0 -> 3.0 substep } } else { // handle cases with significantly fractional mantissa: if (qAbs(fracPart - 0.5) < epsilon) // *.5 mantissa { switch (intPart) { case 1: result = 2; break; // 1.5 -> 0.5 substep case 2: result = 4; break; // 2.5 -> 0.5 substep case 3: result = 4; break; // 3.5 -> 0.7 substep case 4: result = 2; break; // 4.5 -> 1.5 substep case 5: result = 4; break; // 5.5 -> 1.1 substep (won't occur with default getTickStep from here on) case 6: result = 4; break; // 6.5 -> 1.3 substep case 7: result = 2; break; // 7.5 -> 2.5 substep case 8: result = 4; break; // 8.5 -> 1.7 substep case 9: result = 4; break; // 9.5 -> 1.9 substep } } // if mantissa fraction isn't 0.0 or 0.5, don't bother finding good sub tick marks, leave default } return result; } /*! \internal This method returns the tick label string as it should be printed under the \a tick coordinate. If a textual number is returned, it should respect the provided \a locale, \a formatChar and \a precision. If the returned value contains exponentials of the form "2e5" and beautifully typeset powers is enabled in the QCPAxis number format (\ref QCPAxis::setNumberFormat), the exponential part will be formatted accordingly using multiplication symbol and superscript during rendering of the label automatically. */ QString QCPAxisTicker::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision) { return locale.toString(tick, formatChar.toLatin1(), precision); } /*! \internal Returns a vector containing all coordinates of sub ticks that should be drawn. It generates \a subTickCount sub ticks between each tick pair given in \a ticks. If a QCPAxisTicker subclass needs maximal control over the generated sub ticks, it should reimplement this method. Depending on the purpose of the subclass it doesn't necessarily need to base its result on \a subTickCount or \a ticks. */ QVector QCPAxisTicker::createSubTickVector(int subTickCount, const QVector &ticks) { QVector result; if (subTickCount <= 0 || ticks.size() < 2) return result; result.reserve((ticks.size() - 1) * subTickCount); for (int i = 1; i < ticks.size(); ++i) { double subTickStep = (ticks.at(i) - ticks.at(i - 1)) / (double)(subTickCount + 1); for (int k = 1; k <= subTickCount; ++k) result.append(ticks.at(i - 1) + k * subTickStep); } return result; } /*! \internal Returns a vector containing all coordinates of ticks that should be drawn. The default implementation generates ticks with a spacing of \a tickStep (mathematically starting at the tick step origin, see \ref setTickOrigin) distributed over the passed \a range. In order for the axis ticker to generate proper sub ticks, it is necessary that the first and last tick coordinates returned by this method are just below/above the provided \a range. Otherwise the outer intervals won't contain any sub ticks. If a QCPAxisTicker subclass needs maximal control over the generated ticks, it should reimplement this method. Depending on the purpose of the subclass it doesn't necessarily need to base its result on \a tickStep, e.g. when the ticks are spaced unequally like in the case of QCPAxisTickerLog. */ QVector QCPAxisTicker::createTickVector(double tickStep, const QCPRange &range) { QVector result; // Generate tick positions according to tickStep: qint64 firstStep = floor((range.lower - mTickOrigin) / tickStep); // do not use qFloor here, or we'll lose 64 bit precision qint64 lastStep = ceil((range.upper - mTickOrigin) / tickStep); // do not use qCeil here, or we'll lose 64 bit precision int tickcount = lastStep - firstStep + 1; if (tickcount < 0) tickcount = 0; result.resize(tickcount); for (int i = 0; i < tickcount; ++i) result[i] = mTickOrigin + (firstStep + i) * tickStep; return result; } /*! \internal Returns a vector containing all tick label strings corresponding to the tick coordinates provided in \a ticks. The default implementation calls \ref getTickLabel to generate the respective strings. It is possible but uncommon for QCPAxisTicker subclasses to reimplement this method, as reimplementing \ref getTickLabel often achieves the intended result easier. */ QVector QCPAxisTicker::createLabelVector(const QVector &ticks, const QLocale &locale, QChar formatChar, int precision) { QVector result; result.reserve(ticks.size()); for (int i = 0; i < ticks.size(); ++i) result.append(getTickLabel(ticks.at(i), locale, formatChar, precision)); return result; } /*! \internal Removes tick coordinates from \a ticks which lie outside the specified \a range. If \a keepOneOutlier is true, it preserves one tick just outside the range on both sides, if present. The passed \a ticks must be sorted in ascending order. */ void QCPAxisTicker::trimTicks(const QCPRange &range, QVector &ticks, bool keepOneOutlier) const { bool lowFound = false; bool highFound = false; int lowIndex = 0; int highIndex = -1; for (int i = 0; i < ticks.size(); ++i) { if (ticks.at(i) >= range.lower) { lowFound = true; lowIndex = i; break; } } for (int i = ticks.size() - 1; i >= 0; --i) { if (ticks.at(i) <= range.upper) { highFound = true; highIndex = i; break; } } if (highFound && lowFound) { int trimFront = qMax(0, lowIndex - (keepOneOutlier ? 1 : 0)); int trimBack = qMax(0, ticks.size() - (keepOneOutlier ? 2 : 1) - highIndex); if (trimFront > 0 || trimBack > 0) ticks = ticks.mid(trimFront, ticks.size() - trimFront - trimBack); } else // all ticks are either all below or all above the range ticks.clear(); } /*! \internal Returns the coordinate contained in \a candidates which is closest to the provided \a target. This method assumes \a candidates is not empty and sorted in ascending order. */ double QCPAxisTicker::pickClosest(double target, const QVector &candidates) const { if (candidates.size() == 1) return candidates.first(); QVector::const_iterator it = std::lower_bound(candidates.constBegin(), candidates.constEnd(), target); if (it == candidates.constEnd()) return *(it - 1); else if (it == candidates.constBegin()) return *it; else return target - *(it - 1) < *it - target ? *(it - 1) : *it; } /*! \internal Returns the decimal mantissa of \a input. Optionally, if \a magnitude is not set to zero, it also returns the magnitude of \a input as a power of 10. For example, an input of 142.6 will return a mantissa of 1.426 and a magnitude of 100. */ double QCPAxisTicker::getMantissa(double input, double *magnitude) const { const double mag = qPow(10.0, qFloor(qLn(input) / qLn(10.0))); if (magnitude) *magnitude = mag; return input / mag; } /*! \internal Returns a number that is close to \a input but has a clean, easier human readable mantissa. How strongly the mantissa is altered, and thus how strong the result deviates from the original \a input, depends on the current tick step strategy (see \ref setTickStepStrategy). */ double QCPAxisTicker::cleanMantissa(double input) const { double magnitude; const double mantissa = getMantissa(input, &magnitude); switch (mTickStepStrategy) { case tssReadability: { return pickClosest(mantissa, QVector() << 1.0 << 2.0 << 2.5 << 5.0 << 10.0) * magnitude; } case tssMeetTickCount: { // this gives effectively a mantissa of 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 6.0, 8.0, 10.0 if (mantissa <= 5.0) return (int)(mantissa * 2) / 2.0 * magnitude; // round digit after decimal point to 0.5 else return (int)(mantissa / 2.0) * 2.0 * magnitude; // round to first digit in multiples of 2 } } return input; } /* end of 'src/axis/axisticker.cpp' */ /* including file 'src/axis/axistickerdatetime.cpp', size 14443 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxisTickerDateTime //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPAxisTickerDateTime \brief Specialized axis ticker for calendar dates and times as axis ticks \image html axisticker-datetime.png This QCPAxisTicker subclass generates ticks that correspond to real calendar dates and times. The plot axis coordinate is interpreted as Unix Time, so seconds since Epoch (January 1, 1970, 00:00 UTC). This is also used for example by QDateTime in the toTime_t()/setTime_t() methods with a precision of one second. Since Qt 4.7, millisecond accuracy can be obtained from QDateTime by using QDateTime::fromMSecsSinceEpoch()/1000.0. The static methods \ref dateTimeToKey and \ref keyToDateTime conveniently perform this conversion achieving a precision of one millisecond on all Qt versions. The format of the date/time display in the tick labels is controlled with \ref setDateTimeFormat. If a different time spec (time zone) shall be used, see \ref setDateTimeSpec. This ticker produces unequal tick spacing in order to provide intuitive date and time-of-day ticks. For example, if the axis range spans a few years such that there is one tick per year, ticks will be positioned on 1. January of every year. This is intuitive but, due to leap years, will result in slightly unequal tick intervals (visually unnoticeable). The same can be seen in the image above: even though the number of days varies month by month, this ticker generates ticks on the same day of each month. If you would like to change the date/time that is used as a (mathematical) starting date for the ticks, use the \ref setTickOrigin(const QDateTime &origin) method overload, which takes a QDateTime. If you pass 15. July, 9:45 to this method, the yearly ticks will end up on 15. July at 9:45 of every year. The ticker can be created and assigned to an axis like this: \snippet documentation/doc-image-generator/mainwindow.cpp axistickerdatetime-creation \note If you rather wish to display relative times in terms of days, hours, minutes, seconds and milliseconds, and are not interested in the intricacies of real calendar dates with months and (leap) years, have a look at QCPAxisTickerTime instead. */ /*! Constructs the ticker and sets reasonable default values. Axis tickers are commonly created managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker. */ QCPAxisTickerDateTime::QCPAxisTickerDateTime() : mDateTimeFormat(QLatin1String("hh:mm:ss\ndd.MM.yy")), mDateTimeSpec(Qt::LocalTime), mDateStrategy(dsNone) { setTickCount(4); } /*! Sets the format in which dates and times are displayed as tick labels. For details about the \a format string, see the documentation of QDateTime::toString(). Newlines can be inserted with "\n". \see setDateTimeSpec */ void QCPAxisTickerDateTime::setDateTimeFormat(const QString &format) { mDateTimeFormat = format; } /*! Sets the time spec that is used for creating the tick labels from corresponding dates/times. The default value of QDateTime objects (and also QCPAxisTickerDateTime) is Qt::LocalTime. However, if the date time values passed to QCustomPlot (e.g. in the form of axis ranges or keys of a plottable) are given in the UTC spec, set \a spec to Qt::UTC to get the correct axis labels. \see setDateTimeFormat */ void QCPAxisTickerDateTime::setDateTimeSpec(Qt::TimeSpec spec) { mDateTimeSpec = spec; } /*! Sets the tick origin (see \ref QCPAxisTicker::setTickOrigin) in seconds since Epoch (1. Jan 1970, 00:00 UTC). For the date time ticker it might be more intuitive to use the overload which directly takes a QDateTime, see \ref setTickOrigin(const QDateTime &origin). This is useful to define the month/day/time recurring at greater tick interval steps. For example, If you pass 15. July, 9:45 to this method and the tick interval happens to be one tick per year, the ticks will end up on 15. July at 9:45 of every year. */ void QCPAxisTickerDateTime::setTickOrigin(double origin) { QCPAxisTicker::setTickOrigin(origin); } /*! Sets the tick origin (see \ref QCPAxisTicker::setTickOrigin) as a QDateTime \a origin. This is useful to define the month/day/time recurring at greater tick interval steps. For example, If you pass 15. July, 9:45 to this method and the tick interval happens to be one tick per year, the ticks will end up on 15. July at 9:45 of every year. */ void QCPAxisTickerDateTime::setTickOrigin(const QDateTime &origin) { setTickOrigin(dateTimeToKey(origin)); } /*! \internal Returns a sensible tick step with intervals appropriate for a date-time-display, such as weekly, monthly, bi-monthly, etc. Note that this tick step isn't used exactly when generating the tick vector in \ref createTickVector, but only as a guiding value requiring some correction for each individual tick interval. Otherwise this would lead to unintuitive date displays, e.g. jumping between first day in the month to the last day in the previous month from tick to tick, due to the non-uniform length of months. The same problem arises with leap years. \seebaseclassmethod */ double QCPAxisTickerDateTime::getTickStep(const QCPRange &range) { double result = range.size() / (double)(mTickCount + 1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers mDateStrategy = dsNone; if (result < 1) // ideal tick step is below 1 second -> use normal clean mantissa algorithm in units of seconds { result = cleanMantissa(result); } else if (result < 86400 * 30.4375 * 12) // below a year { result = pickClosest( result, QVector() << 1 << 2.5 << 5 << 10 << 15 << 30 << 60 << 2.5 * 60 << 5 * 60 << 10 * 60 << 15 * 60 << 30 * 60 << 60 * 60 // second, minute, hour range << 3600 * 2 << 3600 * 3 << 3600 * 6 << 3600 * 12 << 3600 * 24 // hour to day range << 86400 * 2 << 86400 * 5 << 86400 * 7 << 86400 * 14 << 86400 * 30.4375 << 86400 * 30.4375 * 2 << 86400 * 30.4375 * 3 << 86400 * 30.4375 * 6 << 86400 * 30.4375 * 12); // day, week, month range (avg. days per month includes leap years) if (result > 86400 * 30.4375 - 1) // month tick intervals or larger mDateStrategy = dsUniformDayInMonth; else if (result > 3600 * 24 - 1) // day tick intervals or larger mDateStrategy = dsUniformTimeInDay; } else // more than a year, go back to normal clean mantissa algorithm but in units of years { const double secondsPerYear = 86400 * 30.4375 * 12; // average including leap years result = cleanMantissa(result / secondsPerYear) * secondsPerYear; mDateStrategy = dsUniformDayInMonth; } return result; } /*! \internal Returns a sensible sub tick count with intervals appropriate for a date-time-display, such as weekly, monthly, bi-monthly, etc. \seebaseclassmethod */ int QCPAxisTickerDateTime::getSubTickCount(double tickStep) { int result = QCPAxisTicker::getSubTickCount(tickStep); switch (qRound( tickStep)) // hand chosen subticks for specific minute/hour/day/week/month range (as specified in getTickStep) { case 5 * 60: result = 4; break; case 10 * 60: result = 1; break; case 15 * 60: result = 2; break; case 30 * 60: result = 1; break; case 60 * 60: result = 3; break; case 3600 * 2: result = 3; break; case 3600 * 3: result = 2; break; case 3600 * 6: result = 1; break; case 3600 * 12: result = 3; break; case 3600 * 24: result = 3; break; case 86400 * 2: result = 1; break; case 86400 * 5: result = 4; break; case 86400 * 7: result = 6; break; case 86400 * 14: result = 1; break; case (int)(86400 * 30.4375 + 0.5): result = 3; break; case (int)(86400 * 30.4375 * 2 + 0.5): result = 1; break; case (int)(86400 * 30.4375 * 3 + 0.5): result = 2; break; case (int)(86400 * 30.4375 * 6 + 0.5): result = 5; break; case (int)(86400 * 30.4375 * 12 + 0.5): result = 3; break; } return result; } /*! \internal Generates a date/time tick label for tick coordinate \a tick, based on the currently set format (\ref setDateTimeFormat) and time spec (\ref setDateTimeSpec). \seebaseclassmethod */ QString QCPAxisTickerDateTime::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision) { Q_UNUSED(precision) Q_UNUSED(formatChar) return locale.toString(keyToDateTime(tick).toTimeSpec(mDateTimeSpec), mDateTimeFormat); } /*! \internal Uses the passed \a tickStep as a guiding value and applies corrections in order to obtain non-uniform tick intervals but intuitive tick labels, e.g. falling on the same day of each month. \seebaseclassmethod */ QVector QCPAxisTickerDateTime::createTickVector(double tickStep, const QCPRange &range) { QVector result = QCPAxisTicker::createTickVector(tickStep, range); if (!result.isEmpty()) { if (mDateStrategy == dsUniformTimeInDay) { QDateTime uniformDateTime = keyToDateTime(mTickOrigin); // the time of this datetime will be set for all other ticks, if possible QDateTime tickDateTime; for (int i = 0; i < result.size(); ++i) { tickDateTime = keyToDateTime(result.at(i)); tickDateTime.setTime(uniformDateTime.time()); result[i] = dateTimeToKey(tickDateTime); } } else if (mDateStrategy == dsUniformDayInMonth) { QDateTime uniformDateTime = keyToDateTime(mTickOrigin); // this day (in month) and time will be set for all other ticks, if possible QDateTime tickDateTime; for (int i = 0; i < result.size(); ++i) { tickDateTime = keyToDateTime(result.at(i)); tickDateTime.setTime(uniformDateTime.time()); int thisUniformDay = uniformDateTime.date().day() <= tickDateTime.date().daysInMonth() ? uniformDateTime.date().day() : tickDateTime.date().daysInMonth(); // don't exceed month (e.g. try to set day 31 in February) if (thisUniformDay - tickDateTime.date().day() < -15) // with leap years involved, date month may jump backwards or forwards, and needs to be corrected before setting day tickDateTime = tickDateTime.addMonths(1); else if ( thisUniformDay - tickDateTime.date().day() > 15) // with leap years involved, date month may jump backwards or forwards, and needs to be corrected before setting day tickDateTime = tickDateTime.addMonths(-1); tickDateTime.setDate(QDate(tickDateTime.date().year(), tickDateTime.date().month(), thisUniformDay)); result[i] = dateTimeToKey(tickDateTime); } } } return result; } /*! A convenience method which turns \a key (in seconds since Epoch 1. Jan 1970, 00:00 UTC) into a QDateTime object. This can be used to turn axis coordinates to actual QDateTimes. The accuracy achieved by this method is one millisecond, irrespective of the used Qt version (it works around the lack of a QDateTime::fromMSecsSinceEpoch in Qt 4.6) \see dateTimeToKey */ QDateTime QCPAxisTickerDateTime::keyToDateTime(double key) { #if QT_VERSION < QT_VERSION_CHECK(4, 7, 0) return QDateTime::fromTime_t(key).addMSecs((key - (qint64)key) * 1000); #else return QDateTime::fromMSecsSinceEpoch(key * 1000.0); #endif } /*! \overload A convenience method which turns a QDateTime object into a double value that corresponds to seconds since Epoch (1. Jan 1970, 00:00 UTC). This is the format used as axis coordinates by QCPAxisTickerDateTime. The accuracy achieved by this method is one millisecond, irrespective of the used Qt version (it works around the lack of a QDateTime::toMSecsSinceEpoch in Qt 4.6) \see keyToDateTime */ double QCPAxisTickerDateTime::dateTimeToKey(const QDateTime dateTime) { #if QT_VERSION < QT_VERSION_CHECK(4, 7, 0) return dateTime.toTime_t() + dateTime.time().msec() / 1000.0; #else return dateTime.toMSecsSinceEpoch() / 1000.0; #endif } /*! \overload A convenience method which turns a QDate object into a double value that corresponds to seconds since Epoch (1. Jan 1970, 00:00 UTC). This is the format used as axis coordinates by QCPAxisTickerDateTime. \see keyToDateTime */ double QCPAxisTickerDateTime::dateTimeToKey(const QDate date) { #if QT_VERSION < QT_VERSION_CHECK(4, 7, 0) return QDateTime(date).toTime_t(); #else return QDateTime(date).toMSecsSinceEpoch() / 1000.0; #endif } /* end of 'src/axis/axistickerdatetime.cpp' */ /* including file 'src/axis/axistickertime.cpp', size 11747 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxisTickerTime //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPAxisTickerTime \brief Specialized axis ticker for time spans in units of milliseconds to days \image html axisticker-time.png This QCPAxisTicker subclass generates ticks that corresponds to time intervals. The format of the time display in the tick labels is controlled with \ref setTimeFormat and \ref setFieldWidth. The time coordinate is in the unit of seconds with respect to the time coordinate zero. Unlike with QCPAxisTickerDateTime, the ticks don't correspond to a specific calendar date and time. The time can be displayed in milliseconds, seconds, minutes, hours and days. Depending on the largest available unit in the format specified with \ref setTimeFormat, any time spans above will be carried in that largest unit. So for example if the format string is "%m:%s" and a tick at coordinate value 7815 (being 2 hours, 10 minutes and 15 seconds) is created, the resulting tick label will show "130:15" (130 minutes, 15 seconds). If the format string is "%h:%m:%s", the hour unit will be used and the label will thus be "02:10:15". Negative times with respect to the axis zero will carry a leading minus sign. The ticker can be created and assigned to an axis like this: \snippet documentation/doc-image-generator/mainwindow.cpp axistickertime-creation Here is an example of a time axis providing time information in days, hours and minutes. Due to the axis range spanning a few days and the wanted tick count (\ref setTickCount), the ticker decided to use tick steps of 12 hours: \image html axisticker-time2.png The format string for this example is \snippet documentation/doc-image-generator/mainwindow.cpp axistickertime-creation-2 \note If you rather wish to display calendar dates and times, have a look at QCPAxisTickerDateTime instead. */ /*! Constructs the ticker and sets reasonable default values. Axis tickers are commonly created managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker. */ QCPAxisTickerTime::QCPAxisTickerTime() : mTimeFormat(QLatin1String("%h:%m:%s")), mSmallestUnit(tuSeconds), mBiggestUnit(tuHours) { setTickCount(4); mFieldWidth[tuMilliseconds] = 3; mFieldWidth[tuSeconds] = 2; mFieldWidth[tuMinutes] = 2; mFieldWidth[tuHours] = 2; mFieldWidth[tuDays] = 1; mFormatPattern[tuMilliseconds] = QLatin1String("%z"); mFormatPattern[tuSeconds] = QLatin1String("%s"); mFormatPattern[tuMinutes] = QLatin1String("%m"); mFormatPattern[tuHours] = QLatin1String("%h"); mFormatPattern[tuDays] = QLatin1String("%d"); } /*! Sets the format that will be used to display time in the tick labels. The available patterns are: - %%z for milliseconds - %%s for seconds - %%m for minutes - %%h for hours - %%d for days The field width (zero padding) can be controlled for each unit with \ref setFieldWidth. The largest unit that appears in \a format will carry all the remaining time of a certain tick coordinate, even if it overflows the natural limit of the unit. For example, if %%m is the largest unit it might become larger than 59 in order to consume larger time values. If on the other hand %%h is available, the minutes will wrap around to zero after 59 and the time will carry to the hour digit. */ void QCPAxisTickerTime::setTimeFormat(const QString &format) { mTimeFormat = format; // determine smallest and biggest unit in format, to optimize unit replacement and allow biggest // unit to consume remaining time of a tick value and grow beyond its modulo (e.g. min > 59) mSmallestUnit = tuMilliseconds; mBiggestUnit = tuMilliseconds; bool hasSmallest = false; for (int i = tuMilliseconds; i <= tuDays; ++i) { TimeUnit unit = static_cast(i); if (mTimeFormat.contains(mFormatPattern.value(unit))) { if (!hasSmallest) { mSmallestUnit = unit; hasSmallest = true; } mBiggestUnit = unit; } } } /*! Sets the field widh of the specified \a unit to be \a width digits, when displayed in the tick label. If the number for the specific unit is shorter than \a width, it will be padded with an according number of zeros to the left in order to reach the field width. \see setTimeFormat */ void QCPAxisTickerTime::setFieldWidth(QCPAxisTickerTime::TimeUnit unit, int width) { mFieldWidth[unit] = qMax(width, 1); } /*! \internal Returns the tick step appropriate for time displays, depending on the provided \a range and the smallest available time unit in the current format (\ref setTimeFormat). For example if the unit of seconds isn't available in the format, this method will not generate steps (like 2.5 minutes) that require sub-minute precision to be displayed correctly. \seebaseclassmethod */ double QCPAxisTickerTime::getTickStep(const QCPRange &range) { double result = range.size() / (double)(mTickCount + 1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers if (result < 1) // ideal tick step is below 1 second -> use normal clean mantissa algorithm in units of seconds { if (mSmallestUnit == tuMilliseconds) result = qMax(cleanMantissa(result), 0.001); // smallest tick step is 1 millisecond else // have no milliseconds available in format, so stick with 1 second tickstep result = 1.0; } else if (result < 3600 * 24) // below a day { // the filling of availableSteps seems a bit contorted but it fills in a sorted fashion and thus saves a post-fill sorting run QVector availableSteps; // seconds range: if (mSmallestUnit <= tuSeconds) availableSteps << 1; if (mSmallestUnit == tuMilliseconds) availableSteps << 2.5; // only allow half second steps if milliseconds are there to display it else if (mSmallestUnit == tuSeconds) availableSteps << 2; if (mSmallestUnit <= tuSeconds) availableSteps << 5 << 10 << 15 << 30; // minutes range: if (mSmallestUnit <= tuMinutes) availableSteps << 1 * 60; if (mSmallestUnit <= tuSeconds) availableSteps << 2.5 * 60; // only allow half minute steps if seconds are there to display it else if (mSmallestUnit == tuMinutes) availableSteps << 2 * 60; if (mSmallestUnit <= tuMinutes) availableSteps << 5 * 60 << 10 * 60 << 15 * 60 << 30 * 60; // hours range: if (mSmallestUnit <= tuHours) availableSteps << 1 * 3600 << 2 * 3600 << 3 * 3600 << 6 * 3600 << 12 * 3600 << 24 * 3600; // pick available step that is most appropriate to approximate ideal step: result = pickClosest(result, availableSteps); } else // more than a day, go back to normal clean mantissa algorithm but in units of days { const double secondsPerDay = 3600 * 24; result = cleanMantissa(result / secondsPerDay) * secondsPerDay; } return result; } /*! \internal Returns the sub tick count appropriate for the provided \a tickStep and time displays. \seebaseclassmethod */ int QCPAxisTickerTime::getSubTickCount(double tickStep) { int result = QCPAxisTicker::getSubTickCount(tickStep); switch (qRound(tickStep)) // hand chosen subticks for specific minute/hour/day range (as specified in getTickStep) { case 5 * 60: result = 4; break; case 10 * 60: result = 1; break; case 15 * 60: result = 2; break; case 30 * 60: result = 1; break; case 60 * 60: result = 3; break; case 3600 * 2: result = 3; break; case 3600 * 3: result = 2; break; case 3600 * 6: result = 1; break; case 3600 * 12: result = 3; break; case 3600 * 24: result = 3; break; } return result; } /*! \internal Returns the tick label corresponding to the provided \a tick and the configured format and field widths (\ref setTimeFormat, \ref setFieldWidth). \seebaseclassmethod */ QString QCPAxisTickerTime::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision) { Q_UNUSED(precision) Q_UNUSED(formatChar) Q_UNUSED(locale) bool negative = tick < 0; if (negative) tick *= -1; double values[tuDays + 1]; // contains the msec/sec/min/... value with its respective modulo (e.g. minute 0..59) double restValues [tuDays + 1]; // contains the msec/sec/min/... value as if it's the largest available unit and thus consumes the remaining time restValues[tuMilliseconds] = tick * 1000; values[tuMilliseconds] = modf(restValues[tuMilliseconds] / 1000, &restValues[tuSeconds]) * 1000; values[tuSeconds] = modf(restValues[tuSeconds] / 60, &restValues[tuMinutes]) * 60; values[tuMinutes] = modf(restValues[tuMinutes] / 60, &restValues[tuHours]) * 60; values[tuHours] = modf(restValues[tuHours] / 24, &restValues[tuDays]) * 24; // no need to set values[tuDays] because days are always a rest value (there is no higher unit so it consumes all remaining time) // 2017-07-03: JM force wrap of hours value if (restValues[tuHours] > 24) restValues[tuHours] -= 24; QString result = mTimeFormat; for (int i = mSmallestUnit; i <= mBiggestUnit; ++i) { TimeUnit iUnit = static_cast(i); replaceUnit(result, iUnit, qRound(iUnit == mBiggestUnit ? restValues[iUnit] : values[iUnit])); } if (negative) result.prepend(QLatin1Char('-')); return result; } /*! \internal Replaces all occurrences of the format pattern belonging to \a unit in \a text with the specified \a value, using the field width as specified with \ref setFieldWidth for the \a unit. */ void QCPAxisTickerTime::replaceUnit(QString &text, QCPAxisTickerTime::TimeUnit unit, int value) const { QString valueStr = QString::number(value); while (valueStr.size() < mFieldWidth.value(unit)) valueStr.prepend(QLatin1Char('0')); text.replace(mFormatPattern.value(unit), valueStr); } /* end of 'src/axis/axistickertime.cpp' */ /* including file 'src/axis/axistickerfixed.cpp', size 5583 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxisTickerFixed //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPAxisTickerFixed \brief Specialized axis ticker with a fixed tick step \image html axisticker-fixed.png This QCPAxisTicker subclass generates ticks with a fixed tick step set with \ref setTickStep. It is also possible to allow integer multiples and integer powers of the specified tick step with \ref setScaleStrategy. A typical application of this ticker is to make an axis only display integers, by setting the tick step of the ticker to 1.0 and the scale strategy to \ref ssMultiples. Another case is when a certain number has a special meaning and axis ticks should only appear at multiples of that value. In this case you might also want to consider \ref QCPAxisTickerPi because despite the name it is not limited to only pi symbols/values. The ticker can be created and assigned to an axis like this: \snippet documentation/doc-image-generator/mainwindow.cpp axistickerfixed-creation */ /*! Constructs the ticker and sets reasonable default values. Axis tickers are commonly created managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker. */ QCPAxisTickerFixed::QCPAxisTickerFixed() : mTickStep(1.0), mScaleStrategy(ssNone) { } /*! Sets the fixed tick interval to \a step. The axis ticker will only use this tick step when generating axis ticks. This might cause a very high tick density and overlapping labels if the axis range is zoomed out. Using \ref setScaleStrategy it is possible to relax the fixed step and also allow multiples or powers of \a step. This will enable the ticker to reduce the number of ticks to a reasonable amount (see \ref setTickCount). */ void QCPAxisTickerFixed::setTickStep(double step) { if (step > 0) mTickStep = step; else qDebug() << Q_FUNC_INFO << "tick step must be greater than zero:" << step; } /*! Sets whether the specified tick step (\ref setTickStep) is absolutely fixed or whether modifications may be applied to it before calculating the finally used tick step, such as permitting multiples or powers. See \ref ScaleStrategy for details. The default strategy is \ref ssNone, which means the tick step is absolutely fixed. */ void QCPAxisTickerFixed::setScaleStrategy(QCPAxisTickerFixed::ScaleStrategy strategy) { mScaleStrategy = strategy; } /*! \internal Determines the actually used tick step from the specified tick step and scale strategy (\ref setTickStep, \ref setScaleStrategy). This method either returns the specified tick step exactly, or, if the scale strategy is not \ref ssNone, a modification of it to allow varying the number of ticks in the current axis range. \seebaseclassmethod */ double QCPAxisTickerFixed::getTickStep(const QCPRange &range) { switch (mScaleStrategy) { case ssNone: { return mTickStep; } case ssMultiples: { double exactStep = range.size() / (double)(mTickCount + 1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers if (exactStep < mTickStep) return mTickStep; else return (qint64)(cleanMantissa(exactStep / mTickStep) + 0.5) * mTickStep; } case ssPowers: { double exactStep = range.size() / (double)(mTickCount + 1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers return qPow(mTickStep, (int)(qLn(exactStep) / qLn(mTickStep) + 0.5)); } } return mTickStep; } /* end of 'src/axis/axistickerfixed.cpp' */ /* including file 'src/axis/axistickertext.cpp', size 8653 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxisTickerText //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPAxisTickerText \brief Specialized axis ticker which allows arbitrary labels at specified coordinates \image html axisticker-text.png This QCPAxisTicker subclass generates ticks which can be directly specified by the user as coordinates and associated strings. They can be passed as a whole with \ref setTicks or one at a time with \ref addTick. Alternatively you can directly access the internal storage via \ref ticks and modify the tick/label data there. This is useful for cases where the axis represents categories rather than numerical values. If you are updating the ticks of this ticker regularly and in a dynamic fasion (e.g. dependent on the axis range), it is a sign that you should probably create an own ticker by subclassing QCPAxisTicker, instead of using this one. The ticker can be created and assigned to an axis like this: \snippet documentation/doc-image-generator/mainwindow.cpp axistickertext-creation */ /* start of documentation of inline functions */ /*! \fn QMap &QCPAxisTickerText::ticks() Returns a non-const reference to the internal map which stores the tick coordinates and their labels. You can access the map directly in order to add, remove or manipulate ticks, as an alternative to using the methods provided by QCPAxisTickerText, such as \ref setTicks and \ref addTick. */ /* end of documentation of inline functions */ /*! Constructs the ticker and sets reasonable default values. Axis tickers are commonly created managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker. */ QCPAxisTickerText::QCPAxisTickerText() : mSubTickCount(0) { } /*! \overload Sets the ticks that shall appear on the axis. The map key of \a ticks corresponds to the axis coordinate, and the map value is the string that will appear as tick label. An alternative to manipulate ticks is to directly access the internal storage with the \ref ticks getter. \see addTicks, addTick, clear */ void QCPAxisTickerText::setTicks(const QMap &ticks) { mTicks = ticks; } /*! \overload Sets the ticks that shall appear on the axis. The entries of \a positions correspond to the axis coordinates, and the entries of \a labels are the respective strings that will appear as tick labels. \see addTicks, addTick, clear */ void QCPAxisTickerText::setTicks(const QVector &positions, const QVector labels) { clear(); addTicks(positions, labels); } /*! Sets the number of sub ticks that shall appear between ticks. For QCPAxisTickerText, there is no automatic sub tick count calculation. So if sub ticks are needed, they must be configured with this method. */ void QCPAxisTickerText::setSubTickCount(int subTicks) { if (subTicks >= 0) mSubTickCount = subTicks; else qDebug() << Q_FUNC_INFO << "sub tick count can't be negative:" << subTicks; } /*! Clears all ticks. An alternative to manipulate ticks is to directly access the internal storage with the \ref ticks getter. \see setTicks, addTicks, addTick */ void QCPAxisTickerText::clear() { mTicks.clear(); } /*! Adds a single tick to the axis at the given axis coordinate \a position, with the provided tick \a label. \see addTicks, setTicks, clear */ void QCPAxisTickerText::addTick(double position, QString label) { mTicks.insert(position, label); } /*! \overload Adds the provided \a ticks to the ones already existing. The map key of \a ticks corresponds to the axis coordinate, and the map value is the string that will appear as tick label. An alternative to manipulate ticks is to directly access the internal storage with the \ref ticks getter. \see addTick, setTicks, clear */ void QCPAxisTickerText::addTicks(const QMap &ticks) { mTicks.unite(ticks); } /*! \overload Adds the provided ticks to the ones already existing. The entries of \a positions correspond to the axis coordinates, and the entries of \a labels are the respective strings that will appear as tick labels. An alternative to manipulate ticks is to directly access the internal storage with the \ref ticks getter. \see addTick, setTicks, clear */ void QCPAxisTickerText::addTicks(const QVector &positions, const QVector &labels) { if (positions.size() != labels.size()) qDebug() << Q_FUNC_INFO << "passed unequal length vectors for positions and labels:" << positions.size() << labels.size(); int n = qMin(positions.size(), labels.size()); for (int i = 0; i < n; ++i) mTicks.insert(positions.at(i), labels.at(i)); } /*! Since the tick coordinates are provided externally, this method implementation does nothing. \seebaseclassmethod */ double QCPAxisTickerText::getTickStep(const QCPRange &range) { // text axis ticker has manual tick positions, so doesn't need this method Q_UNUSED(range) return 1.0; } /*! Returns the sub tick count that was configured with \ref setSubTickCount. \seebaseclassmethod */ int QCPAxisTickerText::getSubTickCount(double tickStep) { Q_UNUSED(tickStep) return mSubTickCount; } /*! Returns the tick label which corresponds to the key \a tick in the internal tick storage. Since the labels are provided externally, \a locale, \a formatChar, and \a precision are ignored. \seebaseclassmethod */ QString QCPAxisTickerText::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision) { Q_UNUSED(locale) Q_UNUSED(formatChar) Q_UNUSED(precision) return mTicks.value(tick); } /*! Returns the externally provided tick coordinates which are in the specified \a range. If available, one tick above and below the range is provided in addition, to allow possible sub tick calculation. The parameter \a tickStep is ignored. \seebaseclassmethod */ QVector QCPAxisTickerText::createTickVector(double tickStep, const QCPRange &range) { Q_UNUSED(tickStep) QVector result; if (mTicks.isEmpty()) return result; const QMap constTicks(mTicks); QMap::const_iterator start = constTicks.lowerBound(range.lower); QMap::const_iterator end = constTicks.upperBound(range.upper); // this method should try to give one tick outside of range so proper subticks can be generated: if (start != mTicks.constBegin()) --start; if (end != mTicks.constEnd()) ++end; for (QMap::const_iterator it = start; it != end; ++it) result.append(it.key()); return result; } /* end of 'src/axis/axistickertext.cpp' */ /* including file 'src/axis/axistickerpi.cpp', size 11170 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxisTickerPi //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPAxisTickerPi \brief Specialized axis ticker to display ticks in units of an arbitrary constant, for example pi \image html axisticker-pi.png This QCPAxisTicker subclass generates ticks that are expressed with respect to a given symbolic constant with a numerical value specified with \ref setPiValue and an appearance in the tick labels specified with \ref setPiSymbol. Ticks may be generated at fractions of the symbolic constant. How these fractions appear in the tick label can be configured with \ref setFractionStyle. The ticker can be created and assigned to an axis like this: \snippet documentation/doc-image-generator/mainwindow.cpp axistickerpi-creation */ /*! Constructs the ticker and sets reasonable default values. Axis tickers are commonly created managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker. */ QCPAxisTickerPi::QCPAxisTickerPi() : mPiSymbol(QLatin1String(" ") + QChar(0x03C0)), mPiValue(M_PI), mPeriodicity(0), mFractionStyle(fsUnicodeFractions), mPiTickStep(0) { setTickCount(4); } /*! Sets how the symbol part (which is always a suffix to the number) shall appear in the axis tick label. If a space shall appear between the number and the symbol, make sure the space is contained in \a symbol. */ void QCPAxisTickerPi::setPiSymbol(QString symbol) { mPiSymbol = symbol; } /*! Sets the numerical value that the symbolic constant has. This will be used to place the appropriate fractions of the symbol at the respective axis coordinates. */ void QCPAxisTickerPi::setPiValue(double pi) { mPiValue = pi; } /*! Sets whether the axis labels shall appear periodicly and if so, at which multiplicity of the symbolic constant. To disable periodicity, set \a multiplesOfPi to zero. For example, an axis that identifies 0 with 2pi would set \a multiplesOfPi to two. */ void QCPAxisTickerPi::setPeriodicity(int multiplesOfPi) { mPeriodicity = qAbs(multiplesOfPi); } /*! Sets how the numerical/fractional part preceding the symbolic constant is displayed in tick labels. See \ref FractionStyle for the various options. */ void QCPAxisTickerPi::setFractionStyle(QCPAxisTickerPi::FractionStyle style) { mFractionStyle = style; } /*! \internal Returns the tick step, using the constant's value (\ref setPiValue) as base unit. In consequence the numerical/fractional part preceding the symbolic constant is made to have a readable mantissa. \seebaseclassmethod */ double QCPAxisTickerPi::getTickStep(const QCPRange &range) { mPiTickStep = range.size() / mPiValue / (double)(mTickCount + 1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers mPiTickStep = cleanMantissa(mPiTickStep); return mPiTickStep * mPiValue; } /*! \internal Returns the sub tick count, using the constant's value (\ref setPiValue) as base unit. In consequence the sub ticks divide the numerical/fractional part preceding the symbolic constant reasonably, and not the total tick coordinate. \seebaseclassmethod */ int QCPAxisTickerPi::getSubTickCount(double tickStep) { return QCPAxisTicker::getSubTickCount(tickStep / mPiValue); } /*! \internal Returns the tick label as a fractional/numerical part and a symbolic string as suffix. The formatting of the fraction is done according to the specified \ref setFractionStyle. The appended symbol is specified with \ref setPiSymbol. \seebaseclassmethod */ QString QCPAxisTickerPi::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision) { double tickInPis = tick / mPiValue; if (mPeriodicity > 0) tickInPis = fmod(tickInPis, mPeriodicity); if (mFractionStyle != fsFloatingPoint && mPiTickStep > 0.09 && mPiTickStep < 50) { // simply construct fraction from decimal like 1.234 -> 1234/1000 and then simplify fraction, smaller digits are irrelevant due to mPiTickStep conditional above int denominator = 1000; int numerator = qRound(tickInPis * denominator); simplifyFraction(numerator, denominator); if (qAbs(numerator) == 1 && denominator == 1) return (numerator < 0 ? QLatin1String("-") : QLatin1String("")) + mPiSymbol.trimmed(); else if (numerator == 0) return QLatin1String("0"); else return fractionToString(numerator, denominator) + mPiSymbol; } else { if (qFuzzyIsNull(tickInPis)) return QLatin1String("0"); else if (qFuzzyCompare(qAbs(tickInPis), 1.0)) return (tickInPis < 0 ? QLatin1String("-") : QLatin1String("")) + mPiSymbol.trimmed(); else return QCPAxisTicker::getTickLabel(tickInPis, locale, formatChar, precision) + mPiSymbol; } } /*! \internal Takes the fraction given by \a numerator and \a denominator and modifies the values to make sure the fraction is in irreducible form, i.e. numerator and denominator don't share any common factors which could be cancelled. */ void QCPAxisTickerPi::simplifyFraction(int &numerator, int &denominator) const { if (numerator == 0 || denominator == 0) return; int num = numerator; int denom = denominator; while (denom != 0) // euclidean gcd algorithm { int oldDenom = denom; denom = num % denom; num = oldDenom; } // num is now gcd of numerator and denominator numerator /= num; denominator /= num; } /*! \internal Takes the fraction given by \a numerator and \a denominator and returns a string representation. The result depends on the configured fraction style (\ref setFractionStyle). This method is used to format the numerical/fractional part when generating tick labels. It simplifies the passed fraction to an irreducible form using \ref simplifyFraction and factors out any integer parts of the fraction (e.g. "10/4" becomes "2 1/2"). */ QString QCPAxisTickerPi::fractionToString(int numerator, int denominator) const { if (denominator == 0) { qDebug() << Q_FUNC_INFO << "called with zero denominator"; return QString(); } if (mFractionStyle == fsFloatingPoint) // should never be the case when calling this function { qDebug() << Q_FUNC_INFO << "shouldn't be called with fraction style fsDecimal"; return QString::number(numerator / (double)denominator); // failsafe } int sign = numerator * denominator < 0 ? -1 : 1; numerator = qAbs(numerator); denominator = qAbs(denominator); if (denominator == 1) { return QString::number(sign * numerator); } else { int integerPart = numerator / denominator; int remainder = numerator % denominator; if (remainder == 0) { return QString::number(sign * integerPart); } else { if (mFractionStyle == fsAsciiFractions) { return QString(QLatin1String("%1%2%3/%4")) .arg(sign == -1 ? QLatin1String("-") : QLatin1String("")) .arg(integerPart > 0 ? (QString::number(integerPart) + QLatin1String(" ")) : QString("")) .arg(remainder) .arg(denominator); } else if (mFractionStyle == fsUnicodeFractions) { return QString(QLatin1String("%1%2%3")) .arg(sign == -1 ? QLatin1String("-") : QLatin1String("")) .arg(integerPart > 0 ? QString::number(integerPart) : QLatin1String("")) .arg(unicodeFraction(remainder, denominator)); } } } return QString(); } /*! \internal Returns the unicode string representation of the fraction given by \a numerator and \a denominator. This is the representation used in \ref fractionToString when the fraction style (\ref setFractionStyle) is \ref fsUnicodeFractions. This method doesn't use the single-character common fractions but builds each fraction from a superscript unicode number, the unicode fraction character, and a subscript unicode number. */ QString QCPAxisTickerPi::unicodeFraction(int numerator, int denominator) const { return unicodeSuperscript(numerator) + QChar(0x2044) + unicodeSubscript(denominator); } /*! \internal Returns the unicode string representing \a number as superscript. This is used to build unicode fractions in \ref unicodeFraction. */ QString QCPAxisTickerPi::unicodeSuperscript(int number) const { if (number == 0) return QString(QChar(0x2070)); QString result; while (number > 0) { const int digit = number % 10; switch (digit) { case 1: { result.prepend(QChar(0x00B9)); break; } case 2: { result.prepend(QChar(0x00B2)); break; } case 3: { result.prepend(QChar(0x00B3)); break; } default: { result.prepend(QChar(0x2070 + digit)); break; } } number /= 10; } return result; } /*! \internal Returns the unicode string representing \a number as subscript. This is used to build unicode fractions in \ref unicodeFraction. */ QString QCPAxisTickerPi::unicodeSubscript(int number) const { if (number == 0) return QString(QChar(0x2080)); QString result; while (number > 0) { result.prepend(QChar(0x2080 + number % 10)); number /= 10; } return result; } /* end of 'src/axis/axistickerpi.cpp' */ /* including file 'src/axis/axistickerlog.cpp', size 7106 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxisTickerLog //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPAxisTickerLog \brief Specialized axis ticker suited for logarithmic axes \image html axisticker-log.png This QCPAxisTicker subclass generates ticks with unequal tick intervals suited for logarithmic axis scales. The ticks are placed at powers of the specified log base (\ref setLogBase). Especially in the case of a log base equal to 10 (the default), it might be desirable to have tick labels in the form of powers of ten without mantissa display. To achieve this, set the number precision (\ref QCPAxis::setNumberPrecision) to zero and the number format (\ref QCPAxis::setNumberFormat) to scientific (exponential) display with beautifully typeset decimal powers, so a format string of "eb". This will result in the following axis tick labels: \image html axisticker-log-powers.png The ticker can be created and assigned to an axis like this: \snippet documentation/doc-image-generator/mainwindow.cpp axistickerlog-creation */ /*! Constructs the ticker and sets reasonable default values. Axis tickers are commonly created managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker. */ QCPAxisTickerLog::QCPAxisTickerLog() : mLogBase(10.0), mSubTickCount(8), // generates 10 intervals mLogBaseLnInv(1.0 / qLn(mLogBase)) { } /*! Sets the logarithm base used for tick coordinate generation. The ticks will be placed at integer powers of \a base. */ void QCPAxisTickerLog::setLogBase(double base) { if (base > 0) { mLogBase = base; mLogBaseLnInv = 1.0 / qLn(mLogBase); } else qDebug() << Q_FUNC_INFO << "log base has to be greater than zero:" << base; } /*! Sets the number of sub ticks in a tick interval. Within each interval, the sub ticks are spaced linearly to provide a better visual guide, so the sub tick density increases toward the higher tick. Note that \a subTicks is the number of sub ticks (not sub intervals) in one tick interval. So in the case of logarithm base 10 an intuitive sub tick spacing would be achieved with eight sub ticks (the default). This means e.g. between the ticks 10 and 100 there will be eight ticks, namely at 20, 30, 40, 50, 60, 70, 80 and 90. */ void QCPAxisTickerLog::setSubTickCount(int subTicks) { if (subTicks >= 0) mSubTickCount = subTicks; else qDebug() << Q_FUNC_INFO << "sub tick count can't be negative:" << subTicks; } /*! \internal Since logarithmic tick steps are necessarily different for each tick interval, this method does nothing in the case of QCPAxisTickerLog \seebaseclassmethod */ double QCPAxisTickerLog::getTickStep(const QCPRange &range) { // Logarithmic axis ticker has unequal tick spacing, so doesn't need this method Q_UNUSED(range) return 1.0; } /*! \internal Returns the sub tick count specified in \ref setSubTickCount. For QCPAxisTickerLog, there is no automatic sub tick count calculation necessary. \seebaseclassmethod */ int QCPAxisTickerLog::getSubTickCount(double tickStep) { Q_UNUSED(tickStep) return mSubTickCount; } /*! \internal Creates ticks with a spacing given by the logarithm base and an increasing integer power in the provided \a range. The step in which the power increases tick by tick is chosen in order to keep the total number of ticks as close as possible to the tick count (\ref setTickCount). The parameter \a tickStep is ignored for QCPAxisTickerLog \seebaseclassmethod */ QVector QCPAxisTickerLog::createTickVector(double tickStep, const QCPRange &range) { Q_UNUSED(tickStep) QVector result; if (range.lower > 0 && range.upper > 0) // positive range { double exactPowerStep = qLn(range.upper / range.lower) * mLogBaseLnInv / (double)(mTickCount + 1e-10); double newLogBase = qPow(mLogBase, qMax((int)cleanMantissa(exactPowerStep), 1)); double currentTick = qPow(newLogBase, qFloor(qLn(range.lower) / qLn(newLogBase))); result.append(currentTick); while (currentTick < range.upper && currentTick > 0) // currentMag might be zero for ranges ~1e-300, just cancel in that case { currentTick *= newLogBase; result.append(currentTick); } } else if (range.lower < 0 && range.upper < 0) // negative range { double exactPowerStep = qLn(range.lower / range.upper) * mLogBaseLnInv / (double)(mTickCount + 1e-10); double newLogBase = qPow(mLogBase, qMax((int)cleanMantissa(exactPowerStep), 1)); double currentTick = -qPow(newLogBase, qCeil(qLn(-range.lower) / qLn(newLogBase))); result.append(currentTick); while (currentTick < range.upper && currentTick < 0) // currentMag might be zero for ranges ~1e-300, just cancel in that case { currentTick /= newLogBase; result.append(currentTick); } } else // invalid range for logarithmic scale, because lower and upper have different sign { qDebug() << Q_FUNC_INFO << "Invalid range for logarithmic plot: " << range.lower << ".." << range.upper; } return result; } /* end of 'src/axis/axistickerlog.cpp' */ /* including file 'src/axis/axis.cpp', size 94458 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPGrid //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPGrid \brief Responsible for drawing the grid of a QCPAxis. This class is tightly bound to QCPAxis. Every axis owns a grid instance and uses it to draw the grid lines, sub grid lines and zero-line. You can interact with the grid of an axis via \ref QCPAxis::grid. Normally, you don't need to create an instance of QCPGrid yourself. The axis and grid drawing was split into two classes to allow them to be placed on different layers (both QCPAxis and QCPGrid inherit from QCPLayerable). Thus it is possible to have the grid in the background and the axes in the foreground, and any plottables/items in between. This described situation is the default setup, see the QCPLayer documentation. */ /*! Creates a QCPGrid instance and sets default values. You shouldn't instantiate grids on their own, since every QCPAxis brings its own QCPGrid. */ QCPGrid::QCPGrid(QCPAxis *parentAxis) : QCPLayerable(parentAxis->parentPlot(), QString(), parentAxis), mParentAxis(parentAxis) { // warning: this is called in QCPAxis constructor, so parentAxis members should not be accessed/called setParent(parentAxis); setPen(QPen(QColor(200, 200, 200), 0, Qt::DotLine)); setSubGridPen(QPen(QColor(220, 220, 220), 0, Qt::DotLine)); setZeroLinePen(QPen(QColor(200, 200, 200), 0, Qt::SolidLine)); setSubGridVisible(false); setAntialiased(false); setAntialiasedSubGrid(false); setAntialiasedZeroLine(false); } /*! Sets whether grid lines at sub tick marks are drawn. \see setSubGridPen */ void QCPGrid::setSubGridVisible(bool visible) { mSubGridVisible = visible; } /*! Sets whether sub grid lines are drawn antialiased. */ void QCPGrid::setAntialiasedSubGrid(bool enabled) { mAntialiasedSubGrid = enabled; } /*! Sets whether zero lines are drawn antialiased. */ void QCPGrid::setAntialiasedZeroLine(bool enabled) { mAntialiasedZeroLine = enabled; } /*! Sets the pen with which (major) grid lines are drawn. */ void QCPGrid::setPen(const QPen &pen) { mPen = pen; } /*! Sets the pen with which sub grid lines are drawn. */ void QCPGrid::setSubGridPen(const QPen &pen) { mSubGridPen = pen; } /*! Sets the pen with which zero lines are drawn. Zero lines are lines at value coordinate 0 which may be drawn with a different pen than other grid lines. To disable zero lines and just draw normal grid lines at zero, set \a pen to Qt::NoPen. */ void QCPGrid::setZeroLinePen(const QPen &pen) { mZeroLinePen = pen; } /*! \internal A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter before drawing the major grid lines. This is the antialiasing state the painter passed to the \ref draw method is in by default. This function takes into account the local setting of the antialiasing flag as well as the overrides set with \ref QCustomPlot::setAntialiasedElements and \ref QCustomPlot::setNotAntialiasedElements. \see setAntialiased */ void QCPGrid::applyDefaultAntialiasingHint(QCPPainter *painter) const { applyAntialiasingHint(painter, mAntialiased, QCP::aeGrid); } /*! \internal Draws grid lines and sub grid lines at the positions of (sub) ticks of the parent axis, spanning over the complete axis rect. Also draws the zero line, if appropriate (\ref setZeroLinePen). */ void QCPGrid::draw(QCPPainter *painter) { if (!mParentAxis) { qDebug() << Q_FUNC_INFO << "invalid parent axis"; return; } if (mParentAxis->subTicks() && mSubGridVisible) drawSubGridLines(painter); drawGridLines(painter); } /*! \internal Draws the main grid lines and possibly a zero line with the specified painter. This is a helper function called by \ref draw. */ void QCPGrid::drawGridLines(QCPPainter *painter) const { if (!mParentAxis) { qDebug() << Q_FUNC_INFO << "invalid parent axis"; return; } const int tickCount = mParentAxis->mTickVector.size(); double t; // helper variable, result of coordinate-to-pixel transforms if (mParentAxis->orientation() == Qt::Horizontal) { // draw zeroline: int zeroLineIndex = -1; if (mZeroLinePen.style() != Qt::NoPen && mParentAxis->mRange.lower < 0 && mParentAxis->mRange.upper > 0) { applyAntialiasingHint(painter, mAntialiasedZeroLine, QCP::aeZeroLine); painter->setPen(mZeroLinePen); double epsilon = mParentAxis->range().size() * 1E-6; // for comparing double to zero for (int i = 0; i < tickCount; ++i) { if (qAbs(mParentAxis->mTickVector.at(i)) < epsilon) { zeroLineIndex = i; t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // x painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t, mParentAxis->mAxisRect->top())); break; } } } // draw grid lines: applyDefaultAntialiasingHint(painter); painter->setPen(mPen); for (int i = 0; i < tickCount; ++i) { if (i == zeroLineIndex) continue; // don't draw a gridline on top of the zeroline t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // x painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t, mParentAxis->mAxisRect->top())); } } else { // draw zeroline: int zeroLineIndex = -1; if (mZeroLinePen.style() != Qt::NoPen && mParentAxis->mRange.lower < 0 && mParentAxis->mRange.upper > 0) { applyAntialiasingHint(painter, mAntialiasedZeroLine, QCP::aeZeroLine); painter->setPen(mZeroLinePen); double epsilon = mParentAxis->mRange.size() * 1E-6; // for comparing double to zero for (int i = 0; i < tickCount; ++i) { if (qAbs(mParentAxis->mTickVector.at(i)) < epsilon) { zeroLineIndex = i; t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // y painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t, mParentAxis->mAxisRect->right(), t)); break; } } } // draw grid lines: applyDefaultAntialiasingHint(painter); painter->setPen(mPen); for (int i = 0; i < tickCount; ++i) { if (i == zeroLineIndex) continue; // don't draw a gridline on top of the zeroline t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // y painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t, mParentAxis->mAxisRect->right(), t)); } } } /*! \internal Draws the sub grid lines with the specified painter. This is a helper function called by \ref draw. */ void QCPGrid::drawSubGridLines(QCPPainter *painter) const { if (!mParentAxis) { qDebug() << Q_FUNC_INFO << "invalid parent axis"; return; } applyAntialiasingHint(painter, mAntialiasedSubGrid, QCP::aeSubGrid); double t; // helper variable, result of coordinate-to-pixel transforms painter->setPen(mSubGridPen); if (mParentAxis->orientation() == Qt::Horizontal) { for (int i = 0; i < mParentAxis->mSubTickVector.size(); ++i) { t = mParentAxis->coordToPixel(mParentAxis->mSubTickVector.at(i)); // x painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t, mParentAxis->mAxisRect->top())); } } else { for (int i = 0; i < mParentAxis->mSubTickVector.size(); ++i) { t = mParentAxis->coordToPixel(mParentAxis->mSubTickVector.at(i)); // y painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t, mParentAxis->mAxisRect->right(), t)); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxis //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPAxis \brief Manages a single axis inside a QCustomPlot. Usually doesn't need to be instantiated externally. Access %QCustomPlot's default four axes via QCustomPlot::xAxis (bottom), QCustomPlot::yAxis (left), QCustomPlot::xAxis2 (top) and QCustomPlot::yAxis2 (right). Axes are always part of an axis rect, see QCPAxisRect. \image html AxisNamesOverview.png
Naming convention of axis parts
\n \image html AxisRectSpacingOverview.png
Overview of the spacings and paddings that define the geometry of an axis. The dashed gray line on the left represents the QCustomPlot widget border.
Each axis holds an instance of QCPAxisTicker which is used to generate the tick coordinates and tick labels. You can access the currently installed \ref ticker or set a new one (possibly one of the specialized subclasses, or your own subclass) via \ref setTicker. For details, see the documentation of QCPAxisTicker. */ /* start of documentation of inline functions */ /*! \fn Qt::Orientation QCPAxis::orientation() const Returns the orientation of this axis. The axis orientation (horizontal or vertical) is deduced from the axis type (left, top, right or bottom). \see orientation(AxisType type), pixelOrientation */ /*! \fn QCPGrid *QCPAxis::grid() const Returns the \ref QCPGrid instance belonging to this axis. Access it to set details about the way the grid is displayed. */ /*! \fn static Qt::Orientation QCPAxis::orientation(AxisType type) Returns the orientation of the specified axis type \see orientation(), pixelOrientation */ /*! \fn int QCPAxis::pixelOrientation() const Returns which direction points towards higher coordinate values/keys, in pixel space. This method returns either 1 or -1. If it returns 1, then going in the positive direction along the orientation of the axis in pixels corresponds to going from lower to higher axis coordinates. On the other hand, if this method returns -1, going to smaller pixel values corresponds to going from lower to higher axis coordinates. For example, this is useful to easily shift axis coordinates by a certain amount given in pixels, without having to care about reversed or vertically aligned axes: \code double newKey = keyAxis->pixelToCoord(keyAxis->coordToPixel(oldKey)+10*keyAxis->pixelOrientation()); \endcode \a newKey will then contain a key that is ten pixels towards higher keys, starting from \a oldKey. */ /*! \fn QSharedPointer QCPAxis::ticker() const Returns a modifiable shared pointer to the currently installed axis ticker. The axis ticker is responsible for generating the tick positions and tick labels of this axis. You can access the \ref QCPAxisTicker with this method and modify basic properties such as the approximate tick count (\ref QCPAxisTicker::setTickCount). You can gain more control over the axis ticks by setting a different \ref QCPAxisTicker subclass, see the documentation there. A new axis ticker can be set with \ref setTicker. Since the ticker is stored in the axis as a shared pointer, multiple axes may share the same axis ticker simply by passing the same shared pointer to multiple axes. \see setTicker */ /* end of documentation of inline functions */ /* start of documentation of signals */ /*! \fn void QCPAxis::rangeChanged(const QCPRange &newRange) This signal is emitted when the range of this axis has changed. You can connect it to the \ref setRange slot of another axis to communicate the new range to the other axis, in order for it to be synchronized. You may also manipulate/correct the range with \ref setRange in a slot connected to this signal. This is useful if for example a maximum range span shall not be exceeded, or if the lower/upper range shouldn't go beyond certain values (see \ref QCPRange::bounded). For example, the following slot would limit the x axis to ranges between 0 and 10: \code customPlot->xAxis->setRange(newRange.bounded(0, 10)) \endcode */ /*! \fn void QCPAxis::rangeChanged(const QCPRange &newRange, const QCPRange &oldRange) \overload Additionally to the new range, this signal also provides the previous range held by the axis as \a oldRange. */ /*! \fn void QCPAxis::scaleTypeChanged(QCPAxis::ScaleType scaleType); This signal is emitted when the scale type changes, by calls to \ref setScaleType */ /*! \fn void QCPAxis::selectionChanged(QCPAxis::SelectableParts selection) This signal is emitted when the selection state of this axis has changed, either by user interaction or by a direct call to \ref setSelectedParts. */ /*! \fn void QCPAxis::selectableChanged(const QCPAxis::SelectableParts &parts); This signal is emitted when the selectability changes, by calls to \ref setSelectableParts */ /* end of documentation of signals */ /*! Constructs an Axis instance of Type \a type for the axis rect \a parent. Usually it isn't necessary to instantiate axes directly, because you can let QCustomPlot create them for you with \ref QCPAxisRect::addAxis. If you want to use own QCPAxis-subclasses however, create them manually and then inject them also via \ref QCPAxisRect::addAxis. */ QCPAxis::QCPAxis(QCPAxisRect *parent, AxisType type) : QCPLayerable(parent->parentPlot(), QString(), parent), // axis base: mAxisType(type), mAxisRect(parent), mPadding(5), mOrientation(orientation(type)), mSelectableParts(spAxis | spTickLabels | spAxisLabel), mSelectedParts(spNone), mBasePen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)), mSelectedBasePen(QPen(Qt::blue, 2)), // axis label: mLabel(), mLabelFont(mParentPlot->font()), mSelectedLabelFont(QFont(mLabelFont.family(), mLabelFont.pointSize(), QFont::Bold)), mLabelColor(Qt::black), mSelectedLabelColor(Qt::blue), // tick labels: mTickLabels(true), mTickLabelFont(mParentPlot->font()), mSelectedTickLabelFont(QFont(mTickLabelFont.family(), mTickLabelFont.pointSize(), QFont::Bold)), mTickLabelColor(Qt::black), mSelectedTickLabelColor(Qt::blue), mNumberPrecision(6), mNumberFormatChar('g'), mNumberBeautifulPowers(true), // ticks and subticks: mTicks(true), mSubTicks(true), mTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)), mSelectedTickPen(QPen(Qt::blue, 2)), mSubTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)), mSelectedSubTickPen(QPen(Qt::blue, 2)), // scale and range: mRange(0, 5), mRangeReversed(false), mScaleType(stLinear), // internal members: mGrid(new QCPGrid(this)), mAxisPainter(new QCPAxisPainterPrivate(parent->parentPlot())), mTicker(new QCPAxisTicker), mCachedMarginValid(false), mCachedMargin(0) { setParent(parent); mGrid->setVisible(false); setAntialiased(false); setLayer( mParentPlot ->currentLayer()); // it's actually on that layer already, but we want it in front of the grid, so we place it on there again if (type == atTop) { setTickLabelPadding(3); setLabelPadding(6); } else if (type == atRight) { setTickLabelPadding(7); setLabelPadding(12); } else if (type == atBottom) { setTickLabelPadding(3); setLabelPadding(3); } else if (type == atLeft) { setTickLabelPadding(5); setLabelPadding(10); } } QCPAxis::~QCPAxis() { delete mAxisPainter; delete mGrid; // delete grid here instead of via parent ~QObject for better defined deletion order } /* No documentation as it is a property getter */ int QCPAxis::tickLabelPadding() const { return mAxisPainter->tickLabelPadding; } /* No documentation as it is a property getter */ double QCPAxis::tickLabelRotation() const { return mAxisPainter->tickLabelRotation; } /* No documentation as it is a property getter */ QCPAxis::LabelSide QCPAxis::tickLabelSide() const { return mAxisPainter->tickLabelSide; } /* No documentation as it is a property getter */ QString QCPAxis::numberFormat() const { QString result; result.append(mNumberFormatChar); if (mNumberBeautifulPowers) { result.append(QLatin1Char('b')); if (mAxisPainter->numberMultiplyCross) result.append(QLatin1Char('c')); } return result; } /* No documentation as it is a property getter */ int QCPAxis::tickLengthIn() const { return mAxisPainter->tickLengthIn; } /* No documentation as it is a property getter */ int QCPAxis::tickLengthOut() const { return mAxisPainter->tickLengthOut; } /* No documentation as it is a property getter */ int QCPAxis::subTickLengthIn() const { return mAxisPainter->subTickLengthIn; } /* No documentation as it is a property getter */ int QCPAxis::subTickLengthOut() const { return mAxisPainter->subTickLengthOut; } /* No documentation as it is a property getter */ int QCPAxis::labelPadding() const { return mAxisPainter->labelPadding; } /* No documentation as it is a property getter */ int QCPAxis::offset() const { return mAxisPainter->offset; } /* No documentation as it is a property getter */ QCPLineEnding QCPAxis::lowerEnding() const { return mAxisPainter->lowerEnding; } /* No documentation as it is a property getter */ QCPLineEnding QCPAxis::upperEnding() const { return mAxisPainter->upperEnding; } /*! Sets whether the axis uses a linear scale or a logarithmic scale. Note that this method controls the coordinate transformation. You will likely also want to use a logarithmic tick spacing and labeling, which can be achieved by setting an instance of \ref QCPAxisTickerLog via \ref setTicker. See the documentation of \ref QCPAxisTickerLog about the details of logarithmic axis tick creation. \ref setNumberPrecision */ void QCPAxis::setScaleType(QCPAxis::ScaleType type) { if (mScaleType != type) { mScaleType = type; if (mScaleType == stLogarithmic) setRange(mRange.sanitizedForLogScale()); mCachedMarginValid = false; emit scaleTypeChanged(mScaleType); } } /*! Sets the range of the axis. This slot may be connected with the \ref rangeChanged signal of another axis so this axis is always synchronized with the other axis range, when it changes. To invert the direction of an axis, use \ref setRangeReversed. */ void QCPAxis::setRange(const QCPRange &range) { if (range.lower == mRange.lower && range.upper == mRange.upper) return; if (!QCPRange::validRange(range)) return; QCPRange oldRange = mRange; if (mScaleType == stLogarithmic) { mRange = range.sanitizedForLogScale(); } else { mRange = range.sanitizedForLinScale(); } emit rangeChanged(mRange); emit rangeChanged(mRange, oldRange); } /*! Sets whether the user can (de-)select the parts in \a selectable by clicking on the QCustomPlot surface. (When \ref QCustomPlot::setInteractions contains iSelectAxes.) However, even when \a selectable is set to a value not allowing the selection of a specific part, it is still possible to set the selection of this part manually, by calling \ref setSelectedParts directly. \see SelectablePart, setSelectedParts */ void QCPAxis::setSelectableParts(const SelectableParts &selectable) { if (mSelectableParts != selectable) { mSelectableParts = selectable; emit selectableChanged(mSelectableParts); } } /*! Sets the selected state of the respective axis parts described by \ref SelectablePart. When a part is selected, it uses a different pen/font. The entire selection mechanism for axes is handled automatically when \ref QCustomPlot::setInteractions contains iSelectAxes. You only need to call this function when you wish to change the selection state manually. This function can change the selection state of a part, independent of the \ref setSelectableParts setting. emits the \ref selectionChanged signal when \a selected is different from the previous selection state. \see SelectablePart, setSelectableParts, selectTest, setSelectedBasePen, setSelectedTickPen, setSelectedSubTickPen, setSelectedTickLabelFont, setSelectedLabelFont, setSelectedTickLabelColor, setSelectedLabelColor */ void QCPAxis::setSelectedParts(const SelectableParts &selected) { if (mSelectedParts != selected) { mSelectedParts = selected; emit selectionChanged(mSelectedParts); } } /*! \overload Sets the lower and upper bound of the axis range. To invert the direction of an axis, use \ref setRangeReversed. There is also a slot to set a range, see \ref setRange(const QCPRange &range). */ void QCPAxis::setRange(double lower, double upper) { if (lower == mRange.lower && upper == mRange.upper) return; if (!QCPRange::validRange(lower, upper)) return; QCPRange oldRange = mRange; mRange.lower = lower; mRange.upper = upper; if (mScaleType == stLogarithmic) { mRange = mRange.sanitizedForLogScale(); } else { mRange = mRange.sanitizedForLinScale(); } emit rangeChanged(mRange); emit rangeChanged(mRange, oldRange); } /*! \overload Sets the range of the axis. The \a position coordinate indicates together with the \a alignment parameter, where the new range will be positioned. \a size defines the size of the new axis range. \a alignment may be Qt::AlignLeft, Qt::AlignRight or Qt::AlignCenter. This will cause the left border, right border, or center of the range to be aligned with \a position. Any other values of \a alignment will default to Qt::AlignCenter. */ void QCPAxis::setRange(double position, double size, Qt::AlignmentFlag alignment) { if (alignment == Qt::AlignLeft) setRange(position, position + size); else if (alignment == Qt::AlignRight) setRange(position - size, position); else // alignment == Qt::AlignCenter setRange(position - size / 2.0, position + size / 2.0); } /*! Sets the lower bound of the axis range. The upper bound is not changed. \see setRange */ void QCPAxis::setRangeLower(double lower) { if (mRange.lower == lower) return; QCPRange oldRange = mRange; mRange.lower = lower; if (mScaleType == stLogarithmic) { mRange = mRange.sanitizedForLogScale(); } else { mRange = mRange.sanitizedForLinScale(); } emit rangeChanged(mRange); emit rangeChanged(mRange, oldRange); } /*! Sets the upper bound of the axis range. The lower bound is not changed. \see setRange */ void QCPAxis::setRangeUpper(double upper) { if (mRange.upper == upper) return; QCPRange oldRange = mRange; mRange.upper = upper; if (mScaleType == stLogarithmic) { mRange = mRange.sanitizedForLogScale(); } else { mRange = mRange.sanitizedForLinScale(); } emit rangeChanged(mRange); emit rangeChanged(mRange, oldRange); } /*! Sets whether the axis range (direction) is displayed reversed. Normally, the values on horizontal axes increase left to right, on vertical axes bottom to top. When \a reversed is set to true, the direction of increasing values is inverted. Note that the range and data interface stays the same for reversed axes, e.g. the \a lower part of the \ref setRange interface will still reference the mathematically smaller number than the \a upper part. */ void QCPAxis::setRangeReversed(bool reversed) { mRangeReversed = reversed; } /*! The axis ticker is responsible for generating the tick positions and tick labels. See the documentation of QCPAxisTicker for details on how to work with axis tickers. You can change the tick positioning/labeling behaviour of this axis by setting a different QCPAxisTicker subclass using this method. If you only wish to modify the currently installed axis ticker, access it via \ref ticker. Since the ticker is stored in the axis as a shared pointer, multiple axes may share the same axis ticker simply by passing the same shared pointer to multiple axes. \see ticker */ void QCPAxis::setTicker(QSharedPointer ticker) { if (ticker) mTicker = ticker; else qDebug() << Q_FUNC_INFO << "can not set 0 as axis ticker"; // no need to invalidate margin cache here because produced tick labels are checked for changes in setupTickVector } /*! Sets whether tick marks are displayed. Note that setting \a show to false does not imply that tick labels are invisible, too. To achieve that, see \ref setTickLabels. \see setSubTicks */ void QCPAxis::setTicks(bool show) { if (mTicks != show) { mTicks = show; mCachedMarginValid = false; } } /*! Sets whether tick labels are displayed. Tick labels are the numbers drawn next to tick marks. */ void QCPAxis::setTickLabels(bool show) { if (mTickLabels != show) { mTickLabels = show; mCachedMarginValid = false; if (!mTickLabels) mTickVectorLabels.clear(); } } /*! Sets the distance between the axis base line (including any outward ticks) and the tick labels. \see setLabelPadding, setPadding */ void QCPAxis::setTickLabelPadding(int padding) { if (mAxisPainter->tickLabelPadding != padding) { mAxisPainter->tickLabelPadding = padding; mCachedMarginValid = false; } } /*! Sets the font of the tick labels. \see setTickLabels, setTickLabelColor */ void QCPAxis::setTickLabelFont(const QFont &font) { if (font != mTickLabelFont) { mTickLabelFont = font; mCachedMarginValid = false; } } /*! Sets the color of the tick labels. \see setTickLabels, setTickLabelFont */ void QCPAxis::setTickLabelColor(const QColor &color) { mTickLabelColor = color; } /*! Sets the rotation of the tick labels. If \a degrees is zero, the labels are drawn normally. Else, the tick labels are drawn rotated by \a degrees clockwise. The specified angle is bound to values from -90 to 90 degrees. If \a degrees is exactly -90, 0 or 90, the tick labels are centered on the tick coordinate. For other angles, the label is drawn with an offset such that it seems to point toward or away from the tick mark. */ void QCPAxis::setTickLabelRotation(double degrees) { if (!qFuzzyIsNull(degrees - mAxisPainter->tickLabelRotation)) { mAxisPainter->tickLabelRotation = qBound(-90.0, degrees, 90.0); mCachedMarginValid = false; } } /*! Sets whether the tick labels (numbers) shall appear inside or outside the axis rect. The usual and default setting is \ref lsOutside. Very compact plots sometimes require tick labels to be inside the axis rect, to save space. If \a side is set to \ref lsInside, the tick labels appear on the inside are additionally clipped to the axis rect. */ void QCPAxis::setTickLabelSide(LabelSide side) { mAxisPainter->tickLabelSide = side; mCachedMarginValid = false; } /*! Sets the number format for the numbers in tick labels. This \a formatCode is an extended version of the format code used e.g. by QString::number() and QLocale::toString(). For reference about that, see the "Argument Formats" section in the detailed description of the QString class. \a formatCode is a string of one, two or three characters. The first character is identical to the normal format code used by Qt. In short, this means: 'e'/'E' scientific format, 'f' fixed format, 'g'/'G' scientific or fixed, whichever is shorter. The second and third characters are optional and specific to QCustomPlot:\n If the first char was 'e' or 'g', numbers are/might be displayed in the scientific format, e.g. "5.5e9", which is ugly in a plot. So when the second char of \a formatCode is set to 'b' (for "beautiful"), those exponential numbers are formatted in a more natural way, i.e. "5.5 [multiplication sign] 10 [superscript] 9". By default, the multiplication sign is a centered dot. If instead a cross should be shown (as is usual in the USA), the third char of \a formatCode can be set to 'c'. The inserted multiplication signs are the UTF-8 characters 215 (0xD7) for the cross and 183 (0xB7) for the dot. Examples for \a formatCode: \li \c g normal format code behaviour. If number is small, fixed format is used, if number is large, normal scientific format is used \li \c gb If number is small, fixed format is used, if number is large, scientific format is used with beautifully typeset decimal powers and a dot as multiplication sign \li \c ebc All numbers are in scientific format with beautifully typeset decimal power and a cross as multiplication sign \li \c fb illegal format code, since fixed format doesn't support (or need) beautifully typeset decimal powers. Format code will be reduced to 'f'. \li \c hello illegal format code, since first char is not 'e', 'E', 'f', 'g' or 'G'. Current format code will not be changed. */ void QCPAxis::setNumberFormat(const QString &formatCode) { if (formatCode.isEmpty()) { qDebug() << Q_FUNC_INFO << "Passed formatCode is empty"; return; } mCachedMarginValid = false; // interpret first char as number format char: QString allowedFormatChars(QLatin1String("eEfgG")); if (allowedFormatChars.contains(formatCode.at(0))) { mNumberFormatChar = QLatin1Char(formatCode.at(0).toLatin1()); } else { qDebug() << Q_FUNC_INFO << "Invalid number format code (first char not in 'eEfgG'):" << formatCode; return; } if (formatCode.length() < 2) { mNumberBeautifulPowers = false; mAxisPainter->numberMultiplyCross = false; return; } // interpret second char as indicator for beautiful decimal powers: if (formatCode.at(1) == QLatin1Char('b') && (mNumberFormatChar == QLatin1Char('e') || mNumberFormatChar == QLatin1Char('g'))) { mNumberBeautifulPowers = true; } else { qDebug() << Q_FUNC_INFO << "Invalid number format code (second char not 'b' or first char neither 'e' nor 'g'):" << formatCode; return; } if (formatCode.length() < 3) { mAxisPainter->numberMultiplyCross = false; return; } // interpret third char as indicator for dot or cross multiplication symbol: if (formatCode.at(2) == QLatin1Char('c')) { mAxisPainter->numberMultiplyCross = true; } else if (formatCode.at(2) == QLatin1Char('d')) { mAxisPainter->numberMultiplyCross = false; } else { qDebug() << Q_FUNC_INFO << "Invalid number format code (third char neither 'c' nor 'd'):" << formatCode; return; } } /*! Sets the precision of the tick label numbers. See QLocale::toString(double i, char f, int prec) for details. The effect of precisions are most notably for number Formats starting with 'e', see \ref setNumberFormat */ void QCPAxis::setNumberPrecision(int precision) { if (mNumberPrecision != precision) { mNumberPrecision = precision; mCachedMarginValid = false; } } /*! Sets the length of the ticks in pixels. \a inside is the length the ticks will reach inside the plot and \a outside is the length they will reach outside the plot. If \a outside is greater than zero, the tick labels and axis label will increase their distance to the axis accordingly, so they won't collide with the ticks. \see setSubTickLength, setTickLengthIn, setTickLengthOut */ void QCPAxis::setTickLength(int inside, int outside) { setTickLengthIn(inside); setTickLengthOut(outside); } /*! Sets the length of the inward ticks in pixels. \a inside is the length the ticks will reach inside the plot. \see setTickLengthOut, setTickLength, setSubTickLength */ void QCPAxis::setTickLengthIn(int inside) { if (mAxisPainter->tickLengthIn != inside) { mAxisPainter->tickLengthIn = inside; } } /*! Sets the length of the outward ticks in pixels. \a outside is the length the ticks will reach outside the plot. If \a outside is greater than zero, the tick labels and axis label will increase their distance to the axis accordingly, so they won't collide with the ticks. \see setTickLengthIn, setTickLength, setSubTickLength */ void QCPAxis::setTickLengthOut(int outside) { if (mAxisPainter->tickLengthOut != outside) { mAxisPainter->tickLengthOut = outside; mCachedMarginValid = false; // only outside tick length can change margin } } /*! Sets whether sub tick marks are displayed. Sub ticks are only potentially visible if (major) ticks are also visible (see \ref setTicks) \see setTicks */ void QCPAxis::setSubTicks(bool show) { if (mSubTicks != show) { mSubTicks = show; mCachedMarginValid = false; } } /*! Sets the length of the subticks in pixels. \a inside is the length the subticks will reach inside the plot and \a outside is the length they will reach outside the plot. If \a outside is greater than zero, the tick labels and axis label will increase their distance to the axis accordingly, so they won't collide with the ticks. \see setTickLength, setSubTickLengthIn, setSubTickLengthOut */ void QCPAxis::setSubTickLength(int inside, int outside) { setSubTickLengthIn(inside); setSubTickLengthOut(outside); } /*! Sets the length of the inward subticks in pixels. \a inside is the length the subticks will reach inside the plot. \see setSubTickLengthOut, setSubTickLength, setTickLength */ void QCPAxis::setSubTickLengthIn(int inside) { if (mAxisPainter->subTickLengthIn != inside) { mAxisPainter->subTickLengthIn = inside; } } /*! Sets the length of the outward subticks in pixels. \a outside is the length the subticks will reach outside the plot. If \a outside is greater than zero, the tick labels will increase their distance to the axis accordingly, so they won't collide with the ticks. \see setSubTickLengthIn, setSubTickLength, setTickLength */ void QCPAxis::setSubTickLengthOut(int outside) { if (mAxisPainter->subTickLengthOut != outside) { mAxisPainter->subTickLengthOut = outside; mCachedMarginValid = false; // only outside tick length can change margin } } /*! Sets the pen, the axis base line is drawn with. \see setTickPen, setSubTickPen */ void QCPAxis::setBasePen(const QPen &pen) { mBasePen = pen; } /*! Sets the pen, tick marks will be drawn with. \see setTickLength, setBasePen */ void QCPAxis::setTickPen(const QPen &pen) { mTickPen = pen; } /*! Sets the pen, subtick marks will be drawn with. \see setSubTickCount, setSubTickLength, setBasePen */ void QCPAxis::setSubTickPen(const QPen &pen) { mSubTickPen = pen; } /*! Sets the font of the axis label. \see setLabelColor */ void QCPAxis::setLabelFont(const QFont &font) { if (mLabelFont != font) { mLabelFont = font; mCachedMarginValid = false; } } /*! Sets the color of the axis label. \see setLabelFont */ void QCPAxis::setLabelColor(const QColor &color) { mLabelColor = color; } /*! Sets the text of the axis label that will be shown below/above or next to the axis, depending on its orientation. To disable axis labels, pass an empty string as \a str. */ void QCPAxis::setLabel(const QString &str) { if (mLabel != str) { mLabel = str; mCachedMarginValid = false; } } /*! Sets the distance between the tick labels and the axis label. \see setTickLabelPadding, setPadding */ void QCPAxis::setLabelPadding(int padding) { if (mAxisPainter->labelPadding != padding) { mAxisPainter->labelPadding = padding; mCachedMarginValid = false; } } /*! Sets the padding of the axis. When \ref QCPAxisRect::setAutoMargins is enabled, the padding is the additional outer most space, that is left blank. The axis padding has no meaning if \ref QCPAxisRect::setAutoMargins is disabled. \see setLabelPadding, setTickLabelPadding */ void QCPAxis::setPadding(int padding) { if (mPadding != padding) { mPadding = padding; mCachedMarginValid = false; } } /*! Sets the offset the axis has to its axis rect side. If an axis rect side has multiple axes and automatic margin calculation is enabled for that side, only the offset of the inner most axis has meaning (even if it is set to be invisible). The offset of the other, outer axes is controlled automatically, to place them at appropriate positions. */ void QCPAxis::setOffset(int offset) { mAxisPainter->offset = offset; } /*! Sets the font that is used for tick labels when they are selected. \see setTickLabelFont, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions */ void QCPAxis::setSelectedTickLabelFont(const QFont &font) { if (font != mSelectedTickLabelFont) { mSelectedTickLabelFont = font; // don't set mCachedMarginValid to false here because margin calculation is always done with non-selected fonts } } /*! Sets the font that is used for the axis label when it is selected. \see setLabelFont, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions */ void QCPAxis::setSelectedLabelFont(const QFont &font) { mSelectedLabelFont = font; // don't set mCachedMarginValid to false here because margin calculation is always done with non-selected fonts } /*! Sets the color that is used for tick labels when they are selected. \see setTickLabelColor, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions */ void QCPAxis::setSelectedTickLabelColor(const QColor &color) { if (color != mSelectedTickLabelColor) { mSelectedTickLabelColor = color; } } /*! Sets the color that is used for the axis label when it is selected. \see setLabelColor, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions */ void QCPAxis::setSelectedLabelColor(const QColor &color) { mSelectedLabelColor = color; } /*! Sets the pen that is used to draw the axis base line when selected. \see setBasePen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions */ void QCPAxis::setSelectedBasePen(const QPen &pen) { mSelectedBasePen = pen; } /*! Sets the pen that is used to draw the (major) ticks when selected. \see setTickPen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions */ void QCPAxis::setSelectedTickPen(const QPen &pen) { mSelectedTickPen = pen; } /*! Sets the pen that is used to draw the subticks when selected. \see setSubTickPen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions */ void QCPAxis::setSelectedSubTickPen(const QPen &pen) { mSelectedSubTickPen = pen; } /*! Sets the style for the lower axis ending. See the documentation of QCPLineEnding for available styles. For horizontal axes, this method refers to the left ending, for vertical axes the bottom ending. Note that this meaning does not change when the axis range is reversed with \ref setRangeReversed. \see setUpperEnding */ void QCPAxis::setLowerEnding(const QCPLineEnding &ending) { mAxisPainter->lowerEnding = ending; } /*! Sets the style for the upper axis ending. See the documentation of QCPLineEnding for available styles. For horizontal axes, this method refers to the right ending, for vertical axes the top ending. Note that this meaning does not change when the axis range is reversed with \ref setRangeReversed. \see setLowerEnding */ void QCPAxis::setUpperEnding(const QCPLineEnding &ending) { mAxisPainter->upperEnding = ending; } /*! If the scale type (\ref setScaleType) is \ref stLinear, \a diff is added to the lower and upper bounds of the range. The range is simply moved by \a diff. If the scale type is \ref stLogarithmic, the range bounds are multiplied by \a diff. This corresponds to an apparent "linear" move in logarithmic scaling by a distance of log(diff). */ void QCPAxis::moveRange(double diff) { QCPRange oldRange = mRange; if (mScaleType == stLinear) { mRange.lower += diff; mRange.upper += diff; } else // mScaleType == stLogarithmic { mRange.lower *= diff; mRange.upper *= diff; } emit rangeChanged(mRange); emit rangeChanged(mRange, oldRange); } /*! Scales the range of this axis by \a factor around the center of the current axis range. For example, if \a factor is 2.0, then the axis range will double its size, and the point at the axis range center won't have changed its position in the QCustomPlot widget (i.e. coordinates around the center will have moved symmetrically closer). If you wish to scale around a different coordinate than the current axis range center, use the overload \ref scaleRange(double factor, double center). */ void QCPAxis::scaleRange(double factor) { scaleRange(factor, range().center()); } /*! \overload Scales the range of this axis by \a factor around the coordinate \a center. For example, if \a factor is 2.0, \a center is 1.0, then the axis range will double its size, and the point at coordinate 1.0 won't have changed its position in the QCustomPlot widget (i.e. coordinates around 1.0 will have moved symmetrically closer to 1.0). \see scaleRange(double factor) */ void QCPAxis::scaleRange(double factor, double center) { QCPRange oldRange = mRange; if (mScaleType == stLinear) { QCPRange newRange; newRange.lower = (mRange.lower - center) * factor + center; newRange.upper = (mRange.upper - center) * factor + center; if (QCPRange::validRange(newRange)) mRange = newRange.sanitizedForLinScale(); } else // mScaleType == stLogarithmic { if ((mRange.upper < 0 && center < 0) || (mRange.upper > 0 && center > 0)) // make sure center has same sign as range { QCPRange newRange; newRange.lower = qPow(mRange.lower / center, factor) * center; newRange.upper = qPow(mRange.upper / center, factor) * center; if (QCPRange::validRange(newRange)) mRange = newRange.sanitizedForLogScale(); } else qDebug() << Q_FUNC_INFO << "Center of scaling operation doesn't lie in same logarithmic sign domain as range:" << center; } emit rangeChanged(mRange); emit rangeChanged(mRange, oldRange); } /*! Scales the range of this axis to have a certain scale \a ratio to \a otherAxis. The scaling will be done around the center of the current axis range. For example, if \a ratio is 1, this axis is the \a yAxis and \a otherAxis is \a xAxis, graphs plotted with those axes will appear in a 1:1 aspect ratio, independent of the aspect ratio the axis rect has. This is an operation that changes the range of this axis once, it doesn't fix the scale ratio indefinitely. Note that calling this function in the constructor of the QCustomPlot's parent won't have the desired effect, since the widget dimensions aren't defined yet, and a resizeEvent will follow. */ void QCPAxis::setScaleRatio(const QCPAxis *otherAxis, double ratio) { int otherPixelSize, ownPixelSize; if (otherAxis->orientation() == Qt::Horizontal) otherPixelSize = otherAxis->axisRect()->width(); else otherPixelSize = otherAxis->axisRect()->height(); if (orientation() == Qt::Horizontal) ownPixelSize = axisRect()->width(); else ownPixelSize = axisRect()->height(); double newRangeSize = ratio * otherAxis->range().size() * ownPixelSize / (double)otherPixelSize; setRange(range().center(), newRangeSize, Qt::AlignCenter); } /*! Changes the axis range such that all plottables associated with this axis are fully visible in that dimension. \see QCPAbstractPlottable::rescaleAxes, QCustomPlot::rescaleAxes */ void QCPAxis::rescale(bool onlyVisiblePlottables) { QList p = plottables(); QCPRange newRange; bool haveRange = false; for (int i = 0; i < p.size(); ++i) { if (!p.at(i)->realVisibility() && onlyVisiblePlottables) continue; QCPRange plottableRange; bool currentFoundRange; QCP::SignDomain signDomain = QCP::sdBoth; if (mScaleType == stLogarithmic) signDomain = (mRange.upper < 0 ? QCP::sdNegative : QCP::sdPositive); if (p.at(i)->keyAxis() == this) plottableRange = p.at(i)->getKeyRange(currentFoundRange, signDomain); else plottableRange = p.at(i)->getValueRange(currentFoundRange, signDomain); if (currentFoundRange) { if (!haveRange) newRange = plottableRange; else newRange.expand(plottableRange); haveRange = true; } } if (haveRange) { if (!QCPRange::validRange( newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable { double center = (newRange.lower + newRange.upper) * 0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason if (mScaleType == stLinear) { newRange.lower = center - mRange.size() / 2.0; newRange.upper = center + mRange.size() / 2.0; } else // mScaleType == stLogarithmic { newRange.lower = center / qSqrt(mRange.upper / mRange.lower); newRange.upper = center * qSqrt(mRange.upper / mRange.lower); } } setRange(newRange); } } /*! Transforms \a value, in pixel coordinates of the QCustomPlot widget, to axis coordinates. */ double QCPAxis::pixelToCoord(double value) const { if (orientation() == Qt::Horizontal) { if (mScaleType == stLinear) { if (!mRangeReversed) return (value - mAxisRect->left()) / (double)mAxisRect->width() * mRange.size() + mRange.lower; else return -(value - mAxisRect->left()) / (double)mAxisRect->width() * mRange.size() + mRange.upper; } else // mScaleType == stLogarithmic { if (!mRangeReversed) return qPow(mRange.upper / mRange.lower, (value - mAxisRect->left()) / (double)mAxisRect->width()) * mRange.lower; else return qPow(mRange.upper / mRange.lower, (mAxisRect->left() - value) / (double)mAxisRect->width()) * mRange.upper; } } else // orientation() == Qt::Vertical { if (mScaleType == stLinear) { if (!mRangeReversed) return (mAxisRect->bottom() - value) / (double)mAxisRect->height() * mRange.size() + mRange.lower; else return -(mAxisRect->bottom() - value) / (double)mAxisRect->height() * mRange.size() + mRange.upper; } else // mScaleType == stLogarithmic { if (!mRangeReversed) return qPow(mRange.upper / mRange.lower, (mAxisRect->bottom() - value) / (double)mAxisRect->height()) * mRange.lower; else return qPow(mRange.upper / mRange.lower, (value - mAxisRect->bottom()) / (double)mAxisRect->height()) * mRange.upper; } } } /*! Transforms \a value, in coordinates of the axis, to pixel coordinates of the QCustomPlot widget. */ double QCPAxis::coordToPixel(double value) const { if (orientation() == Qt::Horizontal) { if (mScaleType == stLinear) { if (!mRangeReversed) return (value - mRange.lower) / mRange.size() * mAxisRect->width() + mAxisRect->left(); else return (mRange.upper - value) / mRange.size() * mAxisRect->width() + mAxisRect->left(); } else // mScaleType == stLogarithmic { if (value >= 0 && mRange.upper < 0) // invalid value for logarithmic scale, just draw it outside visible range return !mRangeReversed ? mAxisRect->right() + 200 : mAxisRect->left() - 200; else if (value <= 0 && mRange.upper > 0) // invalid value for logarithmic scale, just draw it outside visible range return !mRangeReversed ? mAxisRect->left() - 200 : mAxisRect->right() + 200; else { if (!mRangeReversed) return qLn(value / mRange.lower) / qLn(mRange.upper / mRange.lower) * mAxisRect->width() + mAxisRect->left(); else return qLn(mRange.upper / value) / qLn(mRange.upper / mRange.lower) * mAxisRect->width() + mAxisRect->left(); } } } else // orientation() == Qt::Vertical { if (mScaleType == stLinear) { if (!mRangeReversed) return mAxisRect->bottom() - (value - mRange.lower) / mRange.size() * mAxisRect->height(); else return mAxisRect->bottom() - (mRange.upper - value) / mRange.size() * mAxisRect->height(); } else // mScaleType == stLogarithmic { if (value >= 0 && mRange.upper < 0) // invalid value for logarithmic scale, just draw it outside visible range return !mRangeReversed ? mAxisRect->top() - 200 : mAxisRect->bottom() + 200; else if (value <= 0 && mRange.upper > 0) // invalid value for logarithmic scale, just draw it outside visible range return !mRangeReversed ? mAxisRect->bottom() + 200 : mAxisRect->top() - 200; else { if (!mRangeReversed) return mAxisRect->bottom() - qLn(value / mRange.lower) / qLn(mRange.upper / mRange.lower) * mAxisRect->height(); else return mAxisRect->bottom() - qLn(mRange.upper / value) / qLn(mRange.upper / mRange.lower) * mAxisRect->height(); } } } } /*! Returns the part of the axis that is hit by \a pos (in pixels). The return value of this function is independent of the user-selectable parts defined with \ref setSelectableParts. Further, this function does not change the current selection state of the axis. If the axis is not visible (\ref setVisible), this function always returns \ref spNone. \see setSelectedParts, setSelectableParts, QCustomPlot::setInteractions */ QCPAxis::SelectablePart QCPAxis::getPartAt(const QPointF &pos) const { if (!mVisible) return spNone; if (mAxisPainter->axisSelectionBox().contains(pos.toPoint())) return spAxis; else if (mAxisPainter->tickLabelsSelectionBox().contains(pos.toPoint())) return spTickLabels; else if (mAxisPainter->labelSelectionBox().contains(pos.toPoint())) return spAxisLabel; else return spNone; } /* inherits documentation from base class */ double QCPAxis::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { if (!mParentPlot) return -1; SelectablePart part = getPartAt(pos); if ((onlySelectable && !mSelectableParts.testFlag(part)) || part == spNone) return -1; if (details) details->setValue(part); return mParentPlot->selectionTolerance() * 0.99; } /*! Returns a list of all the plottables that have this axis as key or value axis. If you are only interested in plottables of type QCPGraph, see \ref graphs. \see graphs, items */ QList QCPAxis::plottables() const { QList result; if (!mParentPlot) return result; for (int i = 0; i < mParentPlot->mPlottables.size(); ++i) { if (mParentPlot->mPlottables.at(i)->keyAxis() == this || mParentPlot->mPlottables.at(i)->valueAxis() == this) result.append(mParentPlot->mPlottables.at(i)); } return result; } /*! Returns a list of all the graphs that have this axis as key or value axis. \see plottables, items */ QList QCPAxis::graphs() const { QList result; if (!mParentPlot) return result; for (int i = 0; i < mParentPlot->mGraphs.size(); ++i) { if (mParentPlot->mGraphs.at(i)->keyAxis() == this || mParentPlot->mGraphs.at(i)->valueAxis() == this) result.append(mParentPlot->mGraphs.at(i)); } return result; } /*! Returns a list of all the items that are associated with this axis. An item is considered associated with an axis if at least one of its positions uses the axis as key or value axis. \see plottables, graphs */ QList QCPAxis::items() const { QList result; if (!mParentPlot) return result; for (int itemId = 0; itemId < mParentPlot->mItems.size(); ++itemId) { QList positions = mParentPlot->mItems.at(itemId)->positions(); for (int posId = 0; posId < positions.size(); ++posId) { if (positions.at(posId)->keyAxis() == this || positions.at(posId)->valueAxis() == this) { result.append(mParentPlot->mItems.at(itemId)); break; } } } return result; } /*! Transforms a margin side to the logically corresponding axis type. (QCP::msLeft to QCPAxis::atLeft, QCP::msRight to QCPAxis::atRight, etc.) */ QCPAxis::AxisType QCPAxis::marginSideToAxisType(QCP::MarginSide side) { switch (side) { case QCP::msLeft: return atLeft; case QCP::msRight: return atRight; case QCP::msTop: return atTop; case QCP::msBottom: return atBottom; default: break; } qDebug() << Q_FUNC_INFO << "Invalid margin side passed:" << (int)side; return atLeft; } /*! Returns the axis type that describes the opposite axis of an axis with the specified \a type. */ QCPAxis::AxisType QCPAxis::opposite(QCPAxis::AxisType type) { switch (type) { case atLeft: return atRight; break; case atRight: return atLeft; break; case atBottom: return atTop; break; case atTop: return atBottom; break; default: qDebug() << Q_FUNC_INFO << "invalid axis type"; return atLeft; break; } } /* inherits documentation from base class */ void QCPAxis::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) { Q_UNUSED(event) SelectablePart part = details.value(); if (mSelectableParts.testFlag(part)) { SelectableParts selBefore = mSelectedParts; setSelectedParts(additive ? mSelectedParts ^ part : part); if (selectionStateChanged) *selectionStateChanged = mSelectedParts != selBefore; } } /* inherits documentation from base class */ void QCPAxis::deselectEvent(bool *selectionStateChanged) { SelectableParts selBefore = mSelectedParts; setSelectedParts(mSelectedParts & ~mSelectableParts); if (selectionStateChanged) *selectionStateChanged = mSelectedParts != selBefore; } /*! \internal A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter before drawing axis lines. This is the antialiasing state the painter passed to the \ref draw method is in by default. This function takes into account the local setting of the antialiasing flag as well as the overrides set with \ref QCustomPlot::setAntialiasedElements and \ref QCustomPlot::setNotAntialiasedElements. \seebaseclassmethod \see setAntialiased */ void QCPAxis::applyDefaultAntialiasingHint(QCPPainter *painter) const { applyAntialiasingHint(painter, mAntialiased, QCP::aeAxes); } /*! \internal Draws the axis with the specified \a painter, using the internal QCPAxisPainterPrivate instance. \seebaseclassmethod */ void QCPAxis::draw(QCPPainter *painter) { QVector subTickPositions; // the final coordToPixel transformed vector passed to QCPAxisPainter QVector tickPositions; // the final coordToPixel transformed vector passed to QCPAxisPainter QVector tickLabels; // the final vector passed to QCPAxisPainter tickPositions.reserve(mTickVector.size()); tickLabels.reserve(mTickVector.size()); subTickPositions.reserve(mSubTickVector.size()); if (mTicks) { for (int i = 0; i < mTickVector.size(); ++i) { tickPositions.append(coordToPixel(mTickVector.at(i))); if (mTickLabels) tickLabels.append(mTickVectorLabels.at(i)); } if (mSubTicks) { const int subTickCount = mSubTickVector.size(); for (int i = 0; i < subTickCount; ++i) subTickPositions.append(coordToPixel(mSubTickVector.at(i))); } } // transfer all properties of this axis to QCPAxisPainterPrivate which it needs to draw the axis. // Note that some axis painter properties are already set by direct feed-through with QCPAxis setters mAxisPainter->type = mAxisType; mAxisPainter->basePen = getBasePen(); mAxisPainter->labelFont = getLabelFont(); mAxisPainter->labelColor = getLabelColor(); mAxisPainter->label = mLabel; mAxisPainter->substituteExponent = mNumberBeautifulPowers; mAxisPainter->tickPen = getTickPen(); mAxisPainter->subTickPen = getSubTickPen(); mAxisPainter->tickLabelFont = getTickLabelFont(); mAxisPainter->tickLabelColor = getTickLabelColor(); mAxisPainter->axisRect = mAxisRect->rect(); mAxisPainter->viewportRect = mParentPlot->viewport(); mAxisPainter->abbreviateDecimalPowers = mScaleType == stLogarithmic; mAxisPainter->reversedEndings = mRangeReversed; mAxisPainter->tickPositions = tickPositions; mAxisPainter->tickLabels = tickLabels; mAxisPainter->subTickPositions = subTickPositions; mAxisPainter->draw(painter); } /*! \internal Prepares the internal tick vector, sub tick vector and tick label vector. This is done by calling QCPAxisTicker::generate on the currently installed ticker. If a change in the label text/count is detected, the cached axis margin is invalidated to make sure the next margin calculation recalculates the label sizes and returns an up-to-date value. */ void QCPAxis::setupTickVectors() { if (!mParentPlot) return; if ((!mTicks && !mTickLabels && !mGrid->visible()) || mRange.size() <= 0) return; QVector oldLabels = mTickVectorLabels; mTicker->generate(mRange, mParentPlot->locale(), mNumberFormatChar, mNumberPrecision, mTickVector, - mSubTicks ? &mSubTickVector : 0, mTickLabels ? &mTickVectorLabels : 0); + mSubTicks ? &mSubTickVector : nullptr, mTickLabels ? &mTickVectorLabels : nullptr); mCachedMarginValid &= mTickVectorLabels == oldLabels; // if labels have changed, margin might have changed, too } /*! \internal Returns the pen that is used to draw the axis base line. Depending on the selection state, this is either mSelectedBasePen or mBasePen. */ QPen QCPAxis::getBasePen() const { return mSelectedParts.testFlag(spAxis) ? mSelectedBasePen : mBasePen; } /*! \internal Returns the pen that is used to draw the (major) ticks. Depending on the selection state, this is either mSelectedTickPen or mTickPen. */ QPen QCPAxis::getTickPen() const { return mSelectedParts.testFlag(spAxis) ? mSelectedTickPen : mTickPen; } /*! \internal Returns the pen that is used to draw the subticks. Depending on the selection state, this is either mSelectedSubTickPen or mSubTickPen. */ QPen QCPAxis::getSubTickPen() const { return mSelectedParts.testFlag(spAxis) ? mSelectedSubTickPen : mSubTickPen; } /*! \internal Returns the font that is used to draw the tick labels. Depending on the selection state, this is either mSelectedTickLabelFont or mTickLabelFont. */ QFont QCPAxis::getTickLabelFont() const { return mSelectedParts.testFlag(spTickLabels) ? mSelectedTickLabelFont : mTickLabelFont; } /*! \internal Returns the font that is used to draw the axis label. Depending on the selection state, this is either mSelectedLabelFont or mLabelFont. */ QFont QCPAxis::getLabelFont() const { return mSelectedParts.testFlag(spAxisLabel) ? mSelectedLabelFont : mLabelFont; } /*! \internal Returns the color that is used to draw the tick labels. Depending on the selection state, this is either mSelectedTickLabelColor or mTickLabelColor. */ QColor QCPAxis::getTickLabelColor() const { return mSelectedParts.testFlag(spTickLabels) ? mSelectedTickLabelColor : mTickLabelColor; } /*! \internal Returns the color that is used to draw the axis label. Depending on the selection state, this is either mSelectedLabelColor or mLabelColor. */ QColor QCPAxis::getLabelColor() const { return mSelectedParts.testFlag(spAxisLabel) ? mSelectedLabelColor : mLabelColor; } /*! \internal Returns the appropriate outward margin for this axis. It is needed if \ref QCPAxisRect::setAutoMargins is set to true on the parent axis rect. An axis with axis type \ref atLeft will return an appropriate left margin, \ref atBottom will return an appropriate bottom margin and so forth. For the calculation, this function goes through similar steps as \ref draw, so changing one function likely requires the modification of the other one as well. The margin consists of the outward tick length, tick label padding, tick label size, label padding, label size, and padding. The margin is cached internally, so repeated calls while leaving the axis range, fonts, etc. unchanged are very fast. */ int QCPAxis::calculateMargin() { if (!mVisible) // if not visible, directly return 0, don't cache 0 because we can't react to setVisible in QCPAxis return 0; if (mCachedMarginValid) return mCachedMargin; // run through similar steps as QCPAxis::draw, and calculate margin needed to fit axis and its labels int margin = 0; QVector tickPositions; // the final coordToPixel transformed vector passed to QCPAxisPainter QVector tickLabels; // the final vector passed to QCPAxisPainter tickPositions.reserve(mTickVector.size()); tickLabels.reserve(mTickVector.size()); if (mTicks) { for (int i = 0; i < mTickVector.size(); ++i) { tickPositions.append(coordToPixel(mTickVector.at(i))); if (mTickLabels) tickLabels.append(mTickVectorLabels.at(i)); } } // transfer all properties of this axis to QCPAxisPainterPrivate which it needs to calculate the size. // Note that some axis painter properties are already set by direct feed-through with QCPAxis setters mAxisPainter->type = mAxisType; mAxisPainter->labelFont = getLabelFont(); mAxisPainter->label = mLabel; mAxisPainter->tickLabelFont = mTickLabelFont; mAxisPainter->axisRect = mAxisRect->rect(); mAxisPainter->viewportRect = mParentPlot->viewport(); mAxisPainter->tickPositions = tickPositions; mAxisPainter->tickLabels = tickLabels; margin += mAxisPainter->size(); margin += mPadding; mCachedMargin = margin; mCachedMarginValid = true; return margin; } /* inherits documentation from base class */ QCP::Interaction QCPAxis::selectionCategory() const { return QCP::iSelectAxes; } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxisPainterPrivate //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPAxisPainterPrivate \internal \brief (Private) This is a private class and not part of the public QCustomPlot interface. It is used by QCPAxis to do the low-level drawing of axis backbone, tick marks, tick labels and axis label. It also buffers the labels to reduce replot times. The parameters are configured by directly accessing the public member variables. */ /*! Constructs a QCPAxisPainterPrivate instance. Make sure to not create a new instance on every redraw, to utilize the caching mechanisms. */ QCPAxisPainterPrivate::QCPAxisPainterPrivate(QCustomPlot *parentPlot) : type(QCPAxis::atLeft), basePen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)), lowerEnding(QCPLineEnding::esNone), upperEnding(QCPLineEnding::esNone), labelPadding(0), tickLabelPadding(0), tickLabelRotation(0), tickLabelSide(QCPAxis::lsOutside), substituteExponent(true), numberMultiplyCross(false), tickLengthIn(5), tickLengthOut(0), subTickLengthIn(2), subTickLengthOut(0), tickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)), subTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)), offset(0), abbreviateDecimalPowers(false), reversedEndings(false), mParentPlot(parentPlot), mLabelCache(16) // cache at most 16 (tick) labels { } QCPAxisPainterPrivate::~QCPAxisPainterPrivate() { } /*! \internal Draws the axis with the specified \a painter. The selection boxes (mAxisSelectionBox, mTickLabelsSelectionBox, mLabelSelectionBox) are set here, too. */ void QCPAxisPainterPrivate::draw(QCPPainter *painter) { QByteArray newHash = generateLabelParameterHash(); if (newHash != mLabelParameterHash) { mLabelCache.clear(); mLabelParameterHash = newHash; } QPoint origin; switch (type) { case QCPAxis::atLeft: origin = axisRect.bottomLeft() + QPoint(-offset, 0); break; case QCPAxis::atRight: origin = axisRect.bottomRight() + QPoint(+offset, 0); break; case QCPAxis::atTop: origin = axisRect.topLeft() + QPoint(0, -offset); break; case QCPAxis::atBottom: origin = axisRect.bottomLeft() + QPoint(0, +offset); break; } double xCor = 0, yCor = 0; // paint system correction, for pixel exact matches (affects baselines and ticks of top/right axes) switch (type) { case QCPAxis::atTop: yCor = -1; break; case QCPAxis::atRight: xCor = 1; break; default: break; } int margin = 0; // draw baseline: QLineF baseLine; painter->setPen(basePen); if (QCPAxis::orientation(type) == Qt::Horizontal) baseLine.setPoints(origin + QPointF(xCor, yCor), origin + QPointF(axisRect.width() + xCor, yCor)); else baseLine.setPoints(origin + QPointF(xCor, yCor), origin + QPointF(xCor, -axisRect.height() + yCor)); if (reversedEndings) baseLine = QLineF(baseLine.p2(), baseLine.p1()); // won't make a difference for line itself, but for line endings later painter->drawLine(baseLine); // draw ticks: if (!tickPositions.isEmpty()) { painter->setPen(tickPen); int tickDir = (type == QCPAxis::atBottom || type == QCPAxis::atRight) ? -1 : 1; // direction of ticks ("inward" is right for left axis and left for right axis) if (QCPAxis::orientation(type) == Qt::Horizontal) { for (int i = 0; i < tickPositions.size(); ++i) painter->drawLine(QLineF(tickPositions.at(i) + xCor, origin.y() - tickLengthOut * tickDir + yCor, tickPositions.at(i) + xCor, origin.y() + tickLengthIn * tickDir + yCor)); } else { for (int i = 0; i < tickPositions.size(); ++i) painter->drawLine(QLineF(origin.x() - tickLengthOut * tickDir + xCor, tickPositions.at(i) + yCor, origin.x() + tickLengthIn * tickDir + xCor, tickPositions.at(i) + yCor)); } } // draw subticks: if (!subTickPositions.isEmpty()) { painter->setPen(subTickPen); // direction of ticks ("inward" is right for left axis and left for right axis) int tickDir = (type == QCPAxis::atBottom || type == QCPAxis::atRight) ? -1 : 1; if (QCPAxis::orientation(type) == Qt::Horizontal) { for (int i = 0; i < subTickPositions.size(); ++i) painter->drawLine(QLineF(subTickPositions.at(i) + xCor, origin.y() - subTickLengthOut * tickDir + yCor, subTickPositions.at(i) + xCor, origin.y() + subTickLengthIn * tickDir + yCor)); } else { for (int i = 0; i < subTickPositions.size(); ++i) painter->drawLine(QLineF(origin.x() - subTickLengthOut * tickDir + xCor, subTickPositions.at(i) + yCor, origin.x() + subTickLengthIn * tickDir + xCor, subTickPositions.at(i) + yCor)); } } margin += qMax(0, qMax(tickLengthOut, subTickLengthOut)); // draw axis base endings: bool antialiasingBackup = painter->antialiasing(); painter->setAntialiasing(true); // always want endings to be antialiased, even if base and ticks themselves aren't painter->setBrush(QBrush(basePen.color())); QCPVector2D baseLineVector(baseLine.dx(), baseLine.dy()); if (lowerEnding.style() != QCPLineEnding::esNone) lowerEnding.draw(painter, QCPVector2D(baseLine.p1()) - baseLineVector.normalized() * lowerEnding.realLength() * (lowerEnding.inverted() ? -1 : 1), -baseLineVector); if (upperEnding.style() != QCPLineEnding::esNone) upperEnding.draw(painter, QCPVector2D(baseLine.p2()) + baseLineVector.normalized() * upperEnding.realLength() * (upperEnding.inverted() ? -1 : 1), baseLineVector); painter->setAntialiasing(antialiasingBackup); // tick labels: QRect oldClipRect; if (tickLabelSide == QCPAxis::lsInside) // if using inside labels, clip them to the axis rect { oldClipRect = painter->clipRegion().boundingRect(); painter->setClipRect(axisRect); } QSize tickLabelsSize(0, 0); // size of largest tick label, for offset calculation of axis label if (!tickLabels.isEmpty()) { if (tickLabelSide == QCPAxis::lsOutside) margin += tickLabelPadding; painter->setFont(tickLabelFont); painter->setPen(QPen(tickLabelColor)); const int maxLabelIndex = qMin(tickPositions.size(), tickLabels.size()); int distanceToAxis = margin; if (tickLabelSide == QCPAxis::lsInside) distanceToAxis = -(qMax(tickLengthIn, subTickLengthIn) + tickLabelPadding); for (int i = 0; i < maxLabelIndex; ++i) placeTickLabel(painter, tickPositions.at(i), distanceToAxis, tickLabels.at(i), &tickLabelsSize); if (tickLabelSide == QCPAxis::lsOutside) margin += (QCPAxis::orientation(type) == Qt::Horizontal) ? tickLabelsSize.height() : tickLabelsSize.width(); } if (tickLabelSide == QCPAxis::lsInside) painter->setClipRect(oldClipRect); // axis label: QRect labelBounds; if (!label.isEmpty()) { margin += labelPadding; painter->setFont(labelFont); painter->setPen(QPen(labelColor)); labelBounds = painter->fontMetrics().boundingRect(0, 0, 0, 0, Qt::TextDontClip, label); if (type == QCPAxis::atLeft) { QTransform oldTransform = painter->transform(); painter->translate((origin.x() - margin - labelBounds.height()), origin.y()); painter->rotate(-90); painter->drawText(0, 0, axisRect.height(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label); painter->setTransform(oldTransform); } else if (type == QCPAxis::atRight) { QTransform oldTransform = painter->transform(); painter->translate((origin.x() + margin + labelBounds.height()), origin.y() - axisRect.height()); painter->rotate(90); painter->drawText(0, 0, axisRect.height(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label); painter->setTransform(oldTransform); } else if (type == QCPAxis::atTop) painter->drawText(origin.x(), origin.y() - margin - labelBounds.height(), axisRect.width(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label); else if (type == QCPAxis::atBottom) painter->drawText(origin.x(), origin.y() + margin, axisRect.width(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label); } // set selection boxes: int selectionTolerance = 0; if (mParentPlot) selectionTolerance = mParentPlot->selectionTolerance(); else qDebug() << Q_FUNC_INFO << "mParentPlot is null"; int selAxisOutSize = qMax(qMax(tickLengthOut, subTickLengthOut), selectionTolerance); int selAxisInSize = selectionTolerance; int selTickLabelSize; int selTickLabelOffset; if (tickLabelSide == QCPAxis::lsOutside) { selTickLabelSize = (QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height() : tickLabelsSize.width()); selTickLabelOffset = qMax(tickLengthOut, subTickLengthOut) + tickLabelPadding; } else { selTickLabelSize = -(QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height() : tickLabelsSize.width()); selTickLabelOffset = -(qMax(tickLengthIn, subTickLengthIn) + tickLabelPadding); } int selLabelSize = labelBounds.height(); int selLabelOffset = qMax(tickLengthOut, subTickLengthOut) + (!tickLabels.isEmpty() && tickLabelSide == QCPAxis::lsOutside ? tickLabelPadding + selTickLabelSize : 0) + labelPadding; if (type == QCPAxis::atLeft) { mAxisSelectionBox.setCoords(origin.x() - selAxisOutSize, axisRect.top(), origin.x() + selAxisInSize, axisRect.bottom()); mTickLabelsSelectionBox.setCoords(origin.x() - selTickLabelOffset - selTickLabelSize, axisRect.top(), origin.x() - selTickLabelOffset, axisRect.bottom()); mLabelSelectionBox.setCoords(origin.x() - selLabelOffset - selLabelSize, axisRect.top(), origin.x() - selLabelOffset, axisRect.bottom()); } else if (type == QCPAxis::atRight) { mAxisSelectionBox.setCoords(origin.x() - selAxisInSize, axisRect.top(), origin.x() + selAxisOutSize, axisRect.bottom()); mTickLabelsSelectionBox.setCoords(origin.x() + selTickLabelOffset + selTickLabelSize, axisRect.top(), origin.x() + selTickLabelOffset, axisRect.bottom()); mLabelSelectionBox.setCoords(origin.x() + selLabelOffset + selLabelSize, axisRect.top(), origin.x() + selLabelOffset, axisRect.bottom()); } else if (type == QCPAxis::atTop) { mAxisSelectionBox.setCoords(axisRect.left(), origin.y() - selAxisOutSize, axisRect.right(), origin.y() + selAxisInSize); mTickLabelsSelectionBox.setCoords(axisRect.left(), origin.y() - selTickLabelOffset - selTickLabelSize, axisRect.right(), origin.y() - selTickLabelOffset); mLabelSelectionBox.setCoords(axisRect.left(), origin.y() - selLabelOffset - selLabelSize, axisRect.right(), origin.y() - selLabelOffset); } else if (type == QCPAxis::atBottom) { mAxisSelectionBox.setCoords(axisRect.left(), origin.y() - selAxisInSize, axisRect.right(), origin.y() + selAxisOutSize); mTickLabelsSelectionBox.setCoords(axisRect.left(), origin.y() + selTickLabelOffset + selTickLabelSize, axisRect.right(), origin.y() + selTickLabelOffset); mLabelSelectionBox.setCoords(axisRect.left(), origin.y() + selLabelOffset + selLabelSize, axisRect.right(), origin.y() + selLabelOffset); } mAxisSelectionBox = mAxisSelectionBox.normalized(); mTickLabelsSelectionBox = mTickLabelsSelectionBox.normalized(); mLabelSelectionBox = mLabelSelectionBox.normalized(); // draw hitboxes for debug purposes: //painter->setBrush(Qt::NoBrush); //painter->drawRects(QVector() << mAxisSelectionBox << mTickLabelsSelectionBox << mLabelSelectionBox); } /*! \internal Returns the size ("margin" in QCPAxisRect context, so measured perpendicular to the axis backbone direction) needed to fit the axis. */ int QCPAxisPainterPrivate::size() const { int result = 0; // get length of tick marks pointing outwards: if (!tickPositions.isEmpty()) result += qMax(0, qMax(tickLengthOut, subTickLengthOut)); // calculate size of tick labels: if (tickLabelSide == QCPAxis::lsOutside) { QSize tickLabelsSize(0, 0); if (!tickLabels.isEmpty()) { for (int i = 0; i < tickLabels.size(); ++i) getMaxTickLabelSize(tickLabelFont, tickLabels.at(i), &tickLabelsSize); result += QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height() : tickLabelsSize.width(); result += tickLabelPadding; } } // calculate size of axis label (only height needed, because left/right labels are rotated by 90 degrees): if (!label.isEmpty()) { QFontMetrics fontMetrics(labelFont); QRect bounds; bounds = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter | Qt::AlignVCenter, label); result += bounds.height() + labelPadding; } return result; } /*! \internal Clears the internal label cache. Upon the next \ref draw, all labels will be created new. This method is called automatically in \ref draw, if any parameters have changed that invalidate the cached labels, such as font, color, etc. */ void QCPAxisPainterPrivate::clearCache() { mLabelCache.clear(); } /*! \internal Returns a hash that allows uniquely identifying whether the label parameters have changed such that the cached labels must be refreshed (\ref clearCache). It is used in \ref draw. If the return value of this method hasn't changed since the last redraw, the respective label parameters haven't changed and cached labels may be used. */ QByteArray QCPAxisPainterPrivate::generateLabelParameterHash() const { QByteArray result; result.append(QByteArray::number(mParentPlot->bufferDevicePixelRatio())); result.append(QByteArray::number(tickLabelRotation)); result.append(QByteArray::number((int)tickLabelSide)); result.append(QByteArray::number((int)substituteExponent)); result.append(QByteArray::number((int)numberMultiplyCross)); result.append(tickLabelColor.name().toLatin1() + QByteArray::number(tickLabelColor.alpha(), 16)); result.append(tickLabelFont.toString().toLatin1()); return result; } /*! \internal Draws a single tick label with the provided \a painter, utilizing the internal label cache to significantly speed up drawing of labels that were drawn in previous calls. The tick label is always bound to an axis, the distance to the axis is controllable via \a distanceToAxis in pixels. The pixel position in the axis direction is passed in the \a position parameter. Hence for the bottom axis, \a position would indicate the horizontal pixel position (not coordinate), at which the label should be drawn. In order to later draw the axis label in a place that doesn't overlap with the tick labels, the largest tick label size is needed. This is acquired by passing a \a tickLabelsSize to the \ref drawTickLabel calls during the process of drawing all tick labels of one axis. In every call, \a tickLabelsSize is expanded, if the drawn label exceeds the value \a tickLabelsSize currently holds. The label is drawn with the font and pen that are currently set on the \a painter. To draw superscripted powers, the font is temporarily made smaller by a fixed factor (see \ref getTickLabelData). */ void QCPAxisPainterPrivate::placeTickLabel(QCPPainter *painter, double position, int distanceToAxis, const QString &text, QSize *tickLabelsSize) { // warning: if you change anything here, also adapt getMaxTickLabelSize() accordingly! if (text.isEmpty()) return; QSize finalSize; QPointF labelAnchor; switch (type) { case QCPAxis::atLeft: labelAnchor = QPointF(axisRect.left() - distanceToAxis - offset, position); break; case QCPAxis::atRight: labelAnchor = QPointF(axisRect.right() + distanceToAxis + offset, position); break; case QCPAxis::atTop: labelAnchor = QPointF(position, axisRect.top() - distanceToAxis - offset); break; case QCPAxis::atBottom: labelAnchor = QPointF(position, axisRect.bottom() + distanceToAxis + offset); break; } if (mParentPlot->plottingHints().testFlag(QCP::phCacheLabels) && !painter->modes().testFlag(QCPPainter::pmNoCaching)) // label caching enabled { CachedLabel *cachedLabel = mLabelCache.take(text); // attempt to get label from cache if (!cachedLabel) // no cached label existed, create it { cachedLabel = new CachedLabel; TickLabelData labelData = getTickLabelData(painter->font(), text); cachedLabel->offset = getTickLabelDrawOffset(labelData) + labelData.rotatedTotalBounds.topLeft(); if (!qFuzzyCompare(1.0, mParentPlot->bufferDevicePixelRatio())) { cachedLabel->pixmap = QPixmap(labelData.rotatedTotalBounds.size() * mParentPlot->bufferDevicePixelRatio()); #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED cachedLabel->pixmap.setDevicePixelRatio(mParentPlot->devicePixelRatio()); #endif } else cachedLabel->pixmap = QPixmap(labelData.rotatedTotalBounds.size()); cachedLabel->pixmap = QPixmap(labelData.rotatedTotalBounds.size()); cachedLabel->pixmap.fill(Qt::transparent); QCPPainter cachePainter(&cachedLabel->pixmap); cachePainter.setPen(painter->pen()); drawTickLabel(&cachePainter, -labelData.rotatedTotalBounds.topLeft().x(), -labelData.rotatedTotalBounds.topLeft().y(), labelData); } // if label would be partly clipped by widget border on sides, don't draw it (only for outside tick labels): bool labelClippedByBorder = false; if (tickLabelSide == QCPAxis::lsOutside) { if (QCPAxis::orientation(type) == Qt::Horizontal) labelClippedByBorder = labelAnchor.x() + cachedLabel->offset.x() + cachedLabel->pixmap.width() / mParentPlot->bufferDevicePixelRatio() > viewportRect.right() || labelAnchor.x() + cachedLabel->offset.x() < viewportRect.left(); else labelClippedByBorder = labelAnchor.y() + cachedLabel->offset.y() + cachedLabel->pixmap.height() / mParentPlot->bufferDevicePixelRatio() > viewportRect.bottom() || labelAnchor.y() + cachedLabel->offset.y() < viewportRect.top(); } if (!labelClippedByBorder) { painter->drawPixmap(labelAnchor + cachedLabel->offset, cachedLabel->pixmap); finalSize = cachedLabel->pixmap.size() / mParentPlot->bufferDevicePixelRatio(); } mLabelCache.insert(text, cachedLabel); // return label to cache or insert for the first time if newly created } else // label caching disabled, draw text directly on surface: { TickLabelData labelData = getTickLabelData(painter->font(), text); QPointF finalPosition = labelAnchor + getTickLabelDrawOffset(labelData); // if label would be partly clipped by widget border on sides, don't draw it (only for outside tick labels): bool labelClippedByBorder = false; if (tickLabelSide == QCPAxis::lsOutside) { if (QCPAxis::orientation(type) == Qt::Horizontal) labelClippedByBorder = finalPosition.x() + (labelData.rotatedTotalBounds.width() + labelData.rotatedTotalBounds.left()) > viewportRect.right() || finalPosition.x() + labelData.rotatedTotalBounds.left() < viewportRect.left(); else labelClippedByBorder = finalPosition.y() + (labelData.rotatedTotalBounds.height() + labelData.rotatedTotalBounds.top()) > viewportRect.bottom() || finalPosition.y() + labelData.rotatedTotalBounds.top() < viewportRect.top(); } if (!labelClippedByBorder) { drawTickLabel(painter, finalPosition.x(), finalPosition.y(), labelData); finalSize = labelData.rotatedTotalBounds.size(); } } // expand passed tickLabelsSize if current tick label is larger: if (finalSize.width() > tickLabelsSize->width()) tickLabelsSize->setWidth(finalSize.width()); if (finalSize.height() > tickLabelsSize->height()) tickLabelsSize->setHeight(finalSize.height()); } /*! \internal This is a \ref placeTickLabel helper function. Draws the tick label specified in \a labelData with \a painter at the pixel positions \a x and \a y. This function is used by \ref placeTickLabel to create new tick labels for the cache, or to directly draw the labels on the QCustomPlot surface when label caching is disabled, i.e. when QCP::phCacheLabels plotting hint is not set. */ void QCPAxisPainterPrivate::drawTickLabel(QCPPainter *painter, double x, double y, const TickLabelData &labelData) const { // backup painter settings that we're about to change: QTransform oldTransform = painter->transform(); QFont oldFont = painter->font(); // transform painter to position/rotation: painter->translate(x, y); if (!qFuzzyIsNull(tickLabelRotation)) painter->rotate(tickLabelRotation); // draw text: if (!labelData.expPart.isEmpty()) // indicator that beautiful powers must be used { painter->setFont(labelData.baseFont); painter->drawText(0, 0, 0, 0, Qt::TextDontClip, labelData.basePart); if (!labelData.suffixPart.isEmpty()) painter->drawText(labelData.baseBounds.width() + 1 + labelData.expBounds.width(), 0, 0, 0, Qt::TextDontClip, labelData.suffixPart); painter->setFont(labelData.expFont); painter->drawText(labelData.baseBounds.width() + 1, 0, labelData.expBounds.width(), labelData.expBounds.height(), Qt::TextDontClip, labelData.expPart); } else { painter->setFont(labelData.baseFont); painter->drawText(0, 0, labelData.totalBounds.width(), labelData.totalBounds.height(), Qt::TextDontClip | Qt::AlignHCenter, labelData.basePart); } // reset painter settings to what it was before: painter->setTransform(oldTransform); painter->setFont(oldFont); } /*! \internal This is a \ref placeTickLabel helper function. Transforms the passed \a text and \a font to a tickLabelData structure that can then be further processed by \ref getTickLabelDrawOffset and \ref drawTickLabel. It splits the text into base and exponent if necessary (member substituteExponent) and calculates appropriate bounding boxes. */ QCPAxisPainterPrivate::TickLabelData QCPAxisPainterPrivate::getTickLabelData(const QFont &font, const QString &text) const { TickLabelData result; // determine whether beautiful decimal powers should be used bool useBeautifulPowers = false; int ePos = -1; // first index of exponent part, text before that will be basePart, text until eLast will be expPart int eLast = -1; // last index of exponent part, rest of text after this will be suffixPart if (substituteExponent) { ePos = text.indexOf(QLatin1Char('e')); if (ePos > 0 && text.at(ePos - 1).isDigit()) { eLast = ePos; while (eLast + 1 < text.size() && (text.at(eLast + 1) == QLatin1Char('+') || text.at(eLast + 1) == QLatin1Char('-') || text.at(eLast + 1).isDigit())) ++eLast; if (eLast > ePos) // only if also to right of 'e' is a digit/+/- interpret it as beautifiable power useBeautifulPowers = true; } } // calculate text bounding rects and do string preparation for beautiful decimal powers: result.baseFont = font; if (result.baseFont.pointSizeF() > 0) // might return -1 if specified with setPixelSize, in that case we can't do correction in next line result.baseFont.setPointSizeF( result.baseFont.pointSizeF() + 0.05); // QFontMetrics.boundingRect has a bug for exact point sizes that make the results oscillate due to internal rounding if (useBeautifulPowers) { // split text into parts of number/symbol that will be drawn normally and part that will be drawn as exponent: result.basePart = text.left(ePos); result.suffixPart = text.mid(eLast + 1); // also drawn normally but after exponent // in log scaling, we want to turn "1*10^n" into "10^n", else add multiplication sign and decimal base: if (abbreviateDecimalPowers && result.basePart == QLatin1String("1")) result.basePart = QLatin1String("10"); else result.basePart += (numberMultiplyCross ? QString(QChar(215)) : QString(QChar(183))) + QLatin1String("10"); result.expPart = text.mid(ePos + 1, eLast - ePos); // clip "+" and leading zeros off expPart: while (result.expPart.length() > 2 && result.expPart.at(1) == QLatin1Char('0')) // length > 2 so we leave one zero when numberFormatChar is 'e' result.expPart.remove(1, 1); if (!result.expPart.isEmpty() && result.expPart.at(0) == QLatin1Char('+')) result.expPart.remove(0, 1); // prepare smaller font for exponent: result.expFont = font; if (result.expFont.pointSize() > 0) result.expFont.setPointSize(result.expFont.pointSize() * 0.75); else result.expFont.setPixelSize(result.expFont.pixelSize() * 0.75); // calculate bounding rects of base part(s), exponent part and total one: result.baseBounds = QFontMetrics(result.baseFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.basePart); result.expBounds = QFontMetrics(result.expFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.expPart); if (!result.suffixPart.isEmpty()) result.suffixBounds = QFontMetrics(result.baseFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.suffixPart); result.totalBounds = result.baseBounds.adjusted( 0, 0, result.expBounds.width() + result.suffixBounds.width() + 2, 0); // +2 consists of the 1 pixel spacing between base and exponent (see drawTickLabel) and an extra pixel to include AA } else // useBeautifulPowers == false { result.basePart = text; result.totalBounds = QFontMetrics(result.baseFont) .boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter, result.basePart); } result.totalBounds.moveTopLeft(QPoint( 0, 0)); // want bounding box aligned top left at origin, independent of how it was created, to make further processing simpler // calculate possibly different bounding rect after rotation: result.rotatedTotalBounds = result.totalBounds; if (!qFuzzyIsNull(tickLabelRotation)) { QTransform transform; transform.rotate(tickLabelRotation); result.rotatedTotalBounds = transform.mapRect(result.rotatedTotalBounds); } return result; } /*! \internal This is a \ref placeTickLabel helper function. Calculates the offset at which the top left corner of the specified tick label shall be drawn. The offset is relative to a point right next to the tick the label belongs to. This function is thus responsible for e.g. centering tick labels under ticks and positioning them appropriately when they are rotated. */ QPointF QCPAxisPainterPrivate::getTickLabelDrawOffset(const TickLabelData &labelData) const { /* calculate label offset from base point at tick (non-trivial, for best visual appearance): short explanation for bottom axis: The anchor, i.e. the point in the label that is placed horizontally under the corresponding tick is always on the label side that is closer to the axis (e.g. the left side of the text when we're rotating clockwise). On that side, the height is halved and the resulting point is defined the anchor. This way, a 90 degree rotated text will be centered under the tick (i.e. displaced horizontally by half its height). At the same time, a 45 degree rotated text will "point toward" its tick, as is typical for rotated tick labels. */ bool doRotation = !qFuzzyIsNull(tickLabelRotation); bool flip = qFuzzyCompare(qAbs(tickLabelRotation), 90.0); // perfect +/-90 degree flip. Indicates vertical label centering on vertical axes. double radians = tickLabelRotation / 180.0 * M_PI; int x = 0, y = 0; if ((type == QCPAxis::atLeft && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atRight && tickLabelSide == QCPAxis::lsInside)) // Anchor at right side of tick label { if (doRotation) { if (tickLabelRotation > 0) { x = -qCos(radians) * labelData.totalBounds.width(); y = flip ? -labelData.totalBounds.width() / 2.0 : -qSin(radians) * labelData.totalBounds.width() - qCos(radians) * labelData.totalBounds.height() / 2.0; } else { x = -qCos(-radians) * labelData.totalBounds.width() - qSin(-radians) * labelData.totalBounds.height(); y = flip ? +labelData.totalBounds.width() / 2.0 : +qSin(-radians) * labelData.totalBounds.width() - qCos(-radians) * labelData.totalBounds.height() / 2.0; } } else { x = -labelData.totalBounds.width(); y = -labelData.totalBounds.height() / 2.0; } } else if ((type == QCPAxis::atRight && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atLeft && tickLabelSide == QCPAxis::lsInside)) // Anchor at left side of tick label { if (doRotation) { if (tickLabelRotation > 0) { x = +qSin(radians) * labelData.totalBounds.height(); y = flip ? -labelData.totalBounds.width() / 2.0 : -qCos(radians) * labelData.totalBounds.height() / 2.0; } else { x = 0; y = flip ? +labelData.totalBounds.width() / 2.0 : -qCos(-radians) * labelData.totalBounds.height() / 2.0; } } else { x = 0; y = -labelData.totalBounds.height() / 2.0; } } else if ((type == QCPAxis::atTop && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atBottom && tickLabelSide == QCPAxis::lsInside)) // Anchor at bottom side of tick label { if (doRotation) { if (tickLabelRotation > 0) { x = -qCos(radians) * labelData.totalBounds.width() + qSin(radians) * labelData.totalBounds.height() / 2.0; y = -qSin(radians) * labelData.totalBounds.width() - qCos(radians) * labelData.totalBounds.height(); } else { x = -qSin(-radians) * labelData.totalBounds.height() / 2.0; y = -qCos(-radians) * labelData.totalBounds.height(); } } else { x = -labelData.totalBounds.width() / 2.0; y = -labelData.totalBounds.height(); } } else if ((type == QCPAxis::atBottom && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atTop && tickLabelSide == QCPAxis::lsInside)) // Anchor at top side of tick label { if (doRotation) { if (tickLabelRotation > 0) { x = +qSin(radians) * labelData.totalBounds.height() / 2.0; y = 0; } else { x = -qCos(-radians) * labelData.totalBounds.width() - qSin(-radians) * labelData.totalBounds.height() / 2.0; y = +qSin(-radians) * labelData.totalBounds.width(); } } else { x = -labelData.totalBounds.width() / 2.0; y = 0; } } return QPointF(x, y); } /*! \internal Simulates the steps done by \ref placeTickLabel by calculating bounding boxes of the text label to be drawn, depending on number format etc. Since only the largest tick label is wanted for the margin calculation, the passed \a tickLabelsSize is only expanded, if it's currently set to a smaller width/height. */ void QCPAxisPainterPrivate::getMaxTickLabelSize(const QFont &font, const QString &text, QSize *tickLabelsSize) const { // note: this function must return the same tick label sizes as the placeTickLabel function. QSize finalSize; if (mParentPlot->plottingHints().testFlag(QCP::phCacheLabels) && mLabelCache.contains(text)) // label caching enabled and have cached label { const CachedLabel *cachedLabel = mLabelCache.object(text); finalSize = cachedLabel->pixmap.size() / mParentPlot->bufferDevicePixelRatio(); } else // label caching disabled or no label with this text cached: { TickLabelData labelData = getTickLabelData(font, text); finalSize = labelData.rotatedTotalBounds.size(); } // expand passed tickLabelsSize if current tick label is larger: if (finalSize.width() > tickLabelsSize->width()) tickLabelsSize->setWidth(finalSize.width()); if (finalSize.height() > tickLabelsSize->height()) tickLabelsSize->setHeight(finalSize.height()); } /* end of 'src/axis/axis.cpp' */ /* including file 'src/scatterstyle.cpp', size 17420 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPScatterStyle //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPScatterStyle \brief Represents the visual appearance of scatter points This class holds information about shape, color and size of scatter points. In plottables like QCPGraph it is used to store how scatter points shall be drawn. For example, \ref QCPGraph::setScatterStyle takes a QCPScatterStyle instance. A scatter style consists of a shape (\ref setShape), a line color (\ref setPen) and possibly a fill (\ref setBrush), if the shape provides a fillable area. Further, the size of the shape can be controlled with \ref setSize. \section QCPScatterStyle-defining Specifying a scatter style You can set all these configurations either by calling the respective functions on an instance: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpscatterstyle-creation-1 Or you can use one of the various constructors that take different parameter combinations, making it easy to specify a scatter style in a single call, like so: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpscatterstyle-creation-2 \section QCPScatterStyle-undefinedpen Leaving the color/pen up to the plottable There are two constructors which leave the pen undefined: \ref QCPScatterStyle() and \ref QCPScatterStyle(ScatterShape shape, double size). If those constructors are used, a call to \ref isPenDefined will return false. It leads to scatter points that inherit the pen from the plottable that uses the scatter style. Thus, if such a scatter style is passed to QCPGraph, the line color of the graph (\ref QCPGraph::setPen) will be used by the scatter points. This makes it very convenient to set up typical scatter settings: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpscatterstyle-shortcreation Notice that it wasn't even necessary to explicitly call a QCPScatterStyle constructor. This works because QCPScatterStyle provides a constructor that can transform a \ref ScatterShape directly into a QCPScatterStyle instance (that's the \ref QCPScatterStyle(ScatterShape shape, double size) constructor with a default for \a size). In those cases, C++ allows directly supplying a \ref ScatterShape, where actually a QCPScatterStyle is expected. \section QCPScatterStyle-custompath-and-pixmap Custom shapes and pixmaps QCPScatterStyle supports drawing custom shapes and arbitrary pixmaps as scatter points. For custom shapes, you can provide a QPainterPath with the desired shape to the \ref setCustomPath function or call the constructor that takes a painter path. The scatter shape will automatically be set to \ref ssCustom. For pixmaps, you call \ref setPixmap with the desired QPixmap. Alternatively you can use the constructor that takes a QPixmap. The scatter shape will automatically be set to \ref ssPixmap. Note that \ref setSize does not influence the appearance of the pixmap. */ /* start documentation of inline functions */ /*! \fn bool QCPScatterStyle::isNone() const Returns whether the scatter shape is \ref ssNone. \see setShape */ /*! \fn bool QCPScatterStyle::isPenDefined() const Returns whether a pen has been defined for this scatter style. The pen is undefined if a constructor is called that does not carry \a pen as parameter. Those are \ref QCPScatterStyle() and \ref QCPScatterStyle(ScatterShape shape, double size). If the pen is undefined, the pen of the respective plottable will be used for drawing scatters. If a pen was defined for this scatter style instance, and you now wish to undefine the pen, call \ref undefinePen. \see setPen */ /* end documentation of inline functions */ /*! Creates a new QCPScatterStyle instance with size set to 6. No shape, pen or brush is defined. Since the pen is undefined (\ref isPenDefined returns false), the scatter color will be inherited from the plottable that uses this scatter style. */ QCPScatterStyle::QCPScatterStyle() : mSize(6), mShape(ssNone), mPen(Qt::NoPen), mBrush(Qt::NoBrush), mPenDefined(false) { } /*! Creates a new QCPScatterStyle instance with shape set to \a shape and size to \a size. No pen or brush is defined. Since the pen is undefined (\ref isPenDefined returns false), the scatter color will be inherited from the plottable that uses this scatter style. */ QCPScatterStyle::QCPScatterStyle(ScatterShape shape, double size) : mSize(size), mShape(shape), mPen(Qt::NoPen), mBrush(Qt::NoBrush), mPenDefined(false) { } /*! Creates a new QCPScatterStyle instance with shape set to \a shape, the pen color set to \a color, and size to \a size. No brush is defined, i.e. the scatter point will not be filled. */ QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QColor &color, double size) : mSize(size), mShape(shape), mPen(QPen(color)), mBrush(Qt::NoBrush), mPenDefined(true) { } /*! Creates a new QCPScatterStyle instance with shape set to \a shape, the pen color set to \a color, the brush color to \a fill (with a solid pattern), and size to \a size. */ QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QColor &color, const QColor &fill, double size) : mSize(size), mShape(shape), mPen(QPen(color)), mBrush(QBrush(fill)), mPenDefined(true) { } /*! Creates a new QCPScatterStyle instance with shape set to \a shape, the pen set to \a pen, the brush to \a brush, and size to \a size. \warning In some cases it might be tempting to directly use a pen style like Qt::NoPen as \a pen and a color like Qt::blue as \a brush. Notice however, that the corresponding call\n QCPScatterStyle(QCPScatterShape::ssCircle, Qt::NoPen, Qt::blue, 5)\n doesn't necessarily lead C++ to use this constructor in some cases, but might mistake Qt::NoPen for a QColor and use the \ref QCPScatterStyle(ScatterShape shape, const QColor &color, const QColor &fill, double size) constructor instead (which will lead to an unexpected look of the scatter points). To prevent this, be more explicit with the parameter types. For example, use QBrush(Qt::blue) instead of just Qt::blue, to clearly point out to the compiler that this constructor is wanted. */ QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QPen &pen, const QBrush &brush, double size) : mSize(size), mShape(shape), mPen(pen), mBrush(brush), mPenDefined(pen.style() != Qt::NoPen) { } /*! Creates a new QCPScatterStyle instance which will show the specified \a pixmap. The scatter shape is set to \ref ssPixmap. */ QCPScatterStyle::QCPScatterStyle(const QPixmap &pixmap) : mSize(5), mShape(ssPixmap), mPen(Qt::NoPen), mBrush(Qt::NoBrush), mPixmap(pixmap), mPenDefined(false) { } /*! Creates a new QCPScatterStyle instance with a custom shape that is defined via \a customPath. The scatter shape is set to \ref ssCustom. The custom shape line will be drawn with \a pen and filled with \a brush. The size has a slightly different meaning than for built-in scatter points: The custom path will be drawn scaled by a factor of \a size/6.0. Since the default \a size is 6, the custom path will appear at a its natural size by default. To double the size of the path for example, set \a size to 12. */ QCPScatterStyle::QCPScatterStyle(const QPainterPath &customPath, const QPen &pen, const QBrush &brush, double size) : mSize(size), mShape(ssCustom), mPen(pen), mBrush(brush), mCustomPath(customPath), mPenDefined(pen.style() != Qt::NoPen) { } /*! Copies the specified \a properties from the \a other scatter style to this scatter style. */ void QCPScatterStyle::setFromOther(const QCPScatterStyle &other, ScatterProperties properties) { if (properties.testFlag(spPen)) { setPen(other.pen()); if (!other.isPenDefined()) undefinePen(); } if (properties.testFlag(spBrush)) setBrush(other.brush()); if (properties.testFlag(spSize)) setSize(other.size()); if (properties.testFlag(spShape)) { setShape(other.shape()); if (other.shape() == ssPixmap) setPixmap(other.pixmap()); else if (other.shape() == ssCustom) setCustomPath(other.customPath()); } } /*! Sets the size (pixel diameter) of the drawn scatter points to \a size. \see setShape */ void QCPScatterStyle::setSize(double size) { mSize = size; } /*! Sets the shape to \a shape. Note that the calls \ref setPixmap and \ref setCustomPath automatically set the shape to \ref ssPixmap and \ref ssCustom, respectively. \see setSize */ void QCPScatterStyle::setShape(QCPScatterStyle::ScatterShape shape) { mShape = shape; } /*! Sets the pen that will be used to draw scatter points to \a pen. If the pen was previously undefined (see \ref isPenDefined), the pen is considered defined after a call to this function, even if \a pen is Qt::NoPen. If you have defined a pen previously by calling this function and now wish to undefine the pen, call \ref undefinePen. \see setBrush */ void QCPScatterStyle::setPen(const QPen &pen) { mPenDefined = true; mPen = pen; } /*! Sets the brush that will be used to fill scatter points to \a brush. Note that not all scatter shapes have fillable areas. For example, \ref ssPlus does not while \ref ssCircle does. \see setPen */ void QCPScatterStyle::setBrush(const QBrush &brush) { mBrush = brush; } /*! Sets the pixmap that will be drawn as scatter point to \a pixmap. Note that \ref setSize does not influence the appearance of the pixmap. The scatter shape is automatically set to \ref ssPixmap. */ void QCPScatterStyle::setPixmap(const QPixmap &pixmap) { setShape(ssPixmap); mPixmap = pixmap; } /*! Sets the custom shape that will be drawn as scatter point to \a customPath. The scatter shape is automatically set to \ref ssCustom. */ void QCPScatterStyle::setCustomPath(const QPainterPath &customPath) { setShape(ssCustom); mCustomPath = customPath; } /*! Sets this scatter style to have an undefined pen (see \ref isPenDefined for what an undefined pen implies). A call to \ref setPen will define a pen. */ void QCPScatterStyle::undefinePen() { mPenDefined = false; } /*! Applies the pen and the brush of this scatter style to \a painter. If this scatter style has an undefined pen (\ref isPenDefined), sets the pen of \a painter to \a defaultPen instead. This function is used by plottables (or any class that wants to draw scatters) just before a number of scatters with this style shall be drawn with the \a painter. \see drawShape */ void QCPScatterStyle::applyTo(QCPPainter *painter, const QPen &defaultPen) const { painter->setPen(mPenDefined ? mPen : defaultPen); painter->setBrush(mBrush); } /*! Draws the scatter shape with \a painter at position \a pos. This function does not modify the pen or the brush on the painter, as \ref applyTo is meant to be called before scatter points are drawn with \ref drawShape. \see applyTo */ void QCPScatterStyle::drawShape(QCPPainter *painter, const QPointF &pos) const { drawShape(painter, pos.x(), pos.y()); } /*! \overload Draws the scatter shape with \a painter at position \a x and \a y. */ void QCPScatterStyle::drawShape(QCPPainter *painter, double x, double y) const { double w = mSize / 2.0; switch (mShape) { case ssNone: break; case ssDot: { painter->drawLine(QPointF(x, y), QPointF(x + 0.0001, y)); break; } case ssCross: { painter->drawLine(QLineF(x - w, y - w, x + w, y + w)); painter->drawLine(QLineF(x - w, y + w, x + w, y - w)); break; } case ssPlus: { painter->drawLine(QLineF(x - w, y, x + w, y)); painter->drawLine(QLineF(x, y + w, x, y - w)); break; } case ssCircle: { painter->drawEllipse(QPointF(x, y), w, w); break; } case ssDisc: { QBrush b = painter->brush(); painter->setBrush(painter->pen().color()); painter->drawEllipse(QPointF(x, y), w, w); painter->setBrush(b); break; } case ssSquare: { painter->drawRect(QRectF(x - w, y - w, mSize, mSize)); break; } case ssDiamond: { painter->drawLine(QLineF(x - w, y, x, y - w)); painter->drawLine(QLineF(x, y - w, x + w, y)); painter->drawLine(QLineF(x + w, y, x, y + w)); painter->drawLine(QLineF(x, y + w, x - w, y)); break; } case ssStar: { painter->drawLine(QLineF(x - w, y, x + w, y)); painter->drawLine(QLineF(x, y + w, x, y - w)); painter->drawLine(QLineF(x - w * 0.707, y - w * 0.707, x + w * 0.707, y + w * 0.707)); painter->drawLine(QLineF(x - w * 0.707, y + w * 0.707, x + w * 0.707, y - w * 0.707)); break; } case ssTriangle: { painter->drawLine(QLineF(x - w, y + 0.755 * w, x + w, y + 0.755 * w)); painter->drawLine(QLineF(x + w, y + 0.755 * w, x, y - 0.977 * w)); painter->drawLine(QLineF(x, y - 0.977 * w, x - w, y + 0.755 * w)); break; } case ssTriangleInverted: { painter->drawLine(QLineF(x - w, y - 0.755 * w, x + w, y - 0.755 * w)); painter->drawLine(QLineF(x + w, y - 0.755 * w, x, y + 0.977 * w)); painter->drawLine(QLineF(x, y + 0.977 * w, x - w, y - 0.755 * w)); break; } case ssCrossSquare: { painter->drawLine(QLineF(x - w, y - w, x + w * 0.95, y + w * 0.95)); painter->drawLine(QLineF(x - w, y + w * 0.95, x + w * 0.95, y - w)); painter->drawRect(QRectF(x - w, y - w, mSize, mSize)); break; } case ssPlusSquare: { painter->drawLine(QLineF(x - w, y, x + w * 0.95, y)); painter->drawLine(QLineF(x, y + w, x, y - w)); painter->drawRect(QRectF(x - w, y - w, mSize, mSize)); break; } case ssCrossCircle: { painter->drawLine(QLineF(x - w * 0.707, y - w * 0.707, x + w * 0.670, y + w * 0.670)); painter->drawLine(QLineF(x - w * 0.707, y + w * 0.670, x + w * 0.670, y - w * 0.707)); painter->drawEllipse(QPointF(x, y), w, w); break; } case ssPlusCircle: { painter->drawLine(QLineF(x - w, y, x + w, y)); painter->drawLine(QLineF(x, y + w, x, y - w)); painter->drawEllipse(QPointF(x, y), w, w); break; } case ssPeace: { painter->drawLine(QLineF(x, y - w, x, y + w)); painter->drawLine(QLineF(x, y, x - w * 0.707, y + w * 0.707)); painter->drawLine(QLineF(x, y, x + w * 0.707, y + w * 0.707)); painter->drawEllipse(QPointF(x, y), w, w); break; } case ssPixmap: { const double widthHalf = mPixmap.width() * 0.5; const double heightHalf = mPixmap.height() * 0.5; #if QT_VERSION < QT_VERSION_CHECK(4, 8, 0) const QRectF clipRect = painter->clipRegion().boundingRect().adjusted(-widthHalf, -heightHalf, widthHalf, heightHalf); #else const QRectF clipRect = painter->clipBoundingRect().adjusted(-widthHalf, -heightHalf, widthHalf, heightHalf); #endif if (clipRect.contains(x, y)) painter->drawPixmap(x - widthHalf, y - heightHalf, mPixmap); break; } case ssCustom: { QTransform oldTransform = painter->transform(); painter->translate(x, y); painter->scale(mSize / 6.0, mSize / 6.0); painter->drawPath(mCustomPath); painter->setTransform(oldTransform); break; } } } /* end of 'src/scatterstyle.cpp' */ //amalgamation: add datacontainer.cpp /* including file 'src/plottable.cpp', size 38861 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPSelectionDecorator //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPSelectionDecorator \brief Controls how a plottable's data selection is drawn Each \ref QCPAbstractPlottable instance has one \ref QCPSelectionDecorator (accessible via \ref QCPAbstractPlottable::selectionDecorator) and uses it when drawing selected segments of its data. The selection decorator controls both pen (\ref setPen) and brush (\ref setBrush), as well as the scatter style (\ref setScatterStyle) if the plottable draws scatters. Since a \ref QCPScatterStyle is itself composed of different properties such as color shape and size, the decorator allows specifying exactly which of those properties shall be used for the selected data point, via \ref setUsedScatterProperties. A \ref QCPSelectionDecorator subclass instance can be passed to a plottable via \ref QCPAbstractPlottable::setSelectionDecorator, allowing greater customizability of the appearance of selected segments. Use \ref copyFrom to easily transfer the settings of one decorator to another one. This is especially useful since plottables take ownership of the passed selection decorator, and thus the same decorator instance can not be passed to multiple plottables. Selection decorators can also themselves perform drawing operations by reimplementing \ref drawDecoration, which is called by the plottable's draw method. The base class \ref QCPSelectionDecorator does not make use of this however. For example, \ref QCPSelectionDecoratorBracket draws brackets around selected data segments. */ /*! Creates a new QCPSelectionDecorator instance with default values */ QCPSelectionDecorator::QCPSelectionDecorator() : mPen(QColor(80, 80, 255), 2.5), mBrush(Qt::NoBrush), mScatterStyle(QCPScatterStyle::ssNone, QPen(Qt::blue, 2), Qt::NoBrush, 6.0), - mUsedScatterProperties(QCPScatterStyle::spPen), mPlottable(0) + mUsedScatterProperties(QCPScatterStyle::spPen), mPlottable(nullptr) { } QCPSelectionDecorator::~QCPSelectionDecorator() { } /*! Sets the pen that will be used by the parent plottable to draw selected data segments. */ void QCPSelectionDecorator::setPen(const QPen &pen) { mPen = pen; } /*! Sets the brush that will be used by the parent plottable to draw selected data segments. */ void QCPSelectionDecorator::setBrush(const QBrush &brush) { mBrush = brush; } /*! Sets the scatter style that will be used by the parent plottable to draw scatters in selected data segments. \a usedProperties specifies which parts of the passed \a scatterStyle will be used by the plottable. The used properties can also be changed via \ref setUsedScatterProperties. */ void QCPSelectionDecorator::setScatterStyle(const QCPScatterStyle &scatterStyle, QCPScatterStyle::ScatterProperties usedProperties) { mScatterStyle = scatterStyle; setUsedScatterProperties(usedProperties); } /*! Use this method to define which properties of the scatter style (set via \ref setScatterStyle) will be used for selected data segments. All properties of the scatter style that are not specified in \a properties will remain as specified in the plottable's original scatter style. */ void QCPSelectionDecorator::setUsedScatterProperties(const QCPScatterStyle::ScatterProperties &properties) { mUsedScatterProperties = properties; } /*! Sets the pen of \a painter to the pen of this selection decorator. \see applyBrush, getFinalScatterStyle */ void QCPSelectionDecorator::applyPen(QCPPainter *painter) const { painter->setPen(mPen); } /*! Sets the brush of \a painter to the brush of this selection decorator. \see applyPen, getFinalScatterStyle */ void QCPSelectionDecorator::applyBrush(QCPPainter *painter) const { painter->setBrush(mBrush); } /*! Returns the scatter style that the parent plottable shall use for selected scatter points. The plottable's original (unselected) scatter style must be passed as \a unselectedStyle. Depending on the setting of \ref setUsedScatterProperties, the returned scatter style is a mixture of this selecion decorator's scatter style (\ref setScatterStyle), and \a unselectedStyle. \see applyPen, applyBrush, setScatterStyle */ QCPScatterStyle QCPSelectionDecorator::getFinalScatterStyle(const QCPScatterStyle &unselectedStyle) const { QCPScatterStyle result(unselectedStyle); result.setFromOther(mScatterStyle, mUsedScatterProperties); // if style shall inherit pen from plottable (has no own pen defined), give it the selected // plottable pen explicitly, so it doesn't use the unselected plottable pen when used in the // plottable: if (!result.isPenDefined()) result.setPen(mPen); return result; } /*! Copies all properties (e.g. color, fill, scatter style) of the \a other selection decorator to this selection decorator. */ void QCPSelectionDecorator::copyFrom(const QCPSelectionDecorator *other) { setPen(other->pen()); setBrush(other->brush()); setScatterStyle(other->scatterStyle(), other->usedScatterProperties()); } /*! This method is called by all plottables' draw methods to allow custom selection decorations to be drawn. Use the passed \a painter to perform the drawing operations. \a selection carries the data selection for which the decoration shall be drawn. The default base class implementation of \ref QCPSelectionDecorator has no special decoration, so this method does nothing. */ void QCPSelectionDecorator::drawDecoration(QCPPainter *painter, QCPDataSelection selection) { Q_UNUSED(painter) Q_UNUSED(selection) } /*! \internal This method is called as soon as a selection decorator is associated with a plottable, by a call to \ref QCPAbstractPlottable::setSelectionDecorator. This way the selection decorator can obtain a pointer to the plottable that uses it (e.g. to access data points via the \ref QCPAbstractPlottable::interface1D interface). If the selection decorator was already added to a different plottable before, this method aborts the registration and returns false. */ bool QCPSelectionDecorator::registerWithPlottable(QCPAbstractPlottable *plottable) { if (!mPlottable) { mPlottable = plottable; return true; } else { qDebug() << Q_FUNC_INFO << "This selection decorator is already registered with plottable:" << reinterpret_cast(mPlottable); return false; } } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAbstractPlottable //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPAbstractPlottable \brief The abstract base class for all data representing objects in a plot. It defines a very basic interface like name, pen, brush, visibility etc. Since this class is abstract, it can't be instantiated. Use one of the subclasses or create a subclass yourself to create new ways of displaying data (see "Creating own plottables" below). Plottables that display one-dimensional data (i.e. data points have a single key dimension and one or multiple values at each key) are based off of the template subclass \ref QCPAbstractPlottable1D, see details there. All further specifics are in the subclasses, for example: \li A normal graph with possibly a line and/or scatter points \ref QCPGraph (typically created with \ref QCustomPlot::addGraph) \li A parametric curve: \ref QCPCurve \li A bar chart: \ref QCPBars \li A statistical box plot: \ref QCPStatisticalBox \li A color encoded two-dimensional map: \ref QCPColorMap \li An OHLC/Candlestick chart: \ref QCPFinancial \section plottables-subclassing Creating own plottables Subclassing directly from QCPAbstractPlottable is only recommended if you wish to display two-dimensional data like \ref QCPColorMap, i.e. two logical key dimensions and one (or more) data dimensions. If you want to display data with only one logical key dimension, you should rather derive from \ref QCPAbstractPlottable1D. If subclassing QCPAbstractPlottable directly, these are the pure virtual functions you must implement: \li \ref selectTest \li \ref draw \li \ref drawLegendIcon \li \ref getKeyRange \li \ref getValueRange See the documentation of those functions for what they need to do. For drawing your plot, you can use the \ref coordsToPixels functions to translate a point in plot coordinates to pixel coordinates. This function is quite convenient, because it takes the orientation of the key and value axes into account for you (x and y are swapped when the key axis is vertical and the value axis horizontal). If you are worried about performance (i.e. you need to translate many points in a loop like QCPGraph), you can directly use \ref QCPAxis::coordToPixel. However, you must then take care about the orientation of the axis yourself. Here are some important members you inherit from QCPAbstractPlottable:
QCustomPlot *\b mParentPlot A pointer to the parent QCustomPlot instance. The parent plot is inferred from the axes that are passed in the constructor.
QString \b mName The name of the plottable.
QPen \b mPen The generic pen of the plottable. You should use this pen for the most prominent data representing lines in the plottable (e.g QCPGraph uses this pen for its graph lines and scatters)
QBrush \b mBrush The generic brush of the plottable. You should use this brush for the most prominent fillable structures in the plottable (e.g. QCPGraph uses this brush to control filling under the graph)
QPointer<\ref QCPAxis> \b mKeyAxis, \b mValueAxis The key and value axes this plottable is attached to. Call their QCPAxis::coordToPixel functions to translate coordinates to pixels in either the key or value dimension. Make sure to check whether the pointer is null before using it. If one of the axes is null, don't draw the plottable.
\ref QCPSelectionDecorator \b mSelectionDecorator The currently set selection decorator which specifies how selected data of the plottable shall be drawn and decorated. When drawing your data, you must consult this decorator for the appropriate pen/brush before drawing unselected/selected data segments. Finally, you should call its \ref QCPSelectionDecorator::drawDecoration method at the end of your \ref draw implementation.
\ref QCP::SelectionType \b mSelectable In which composition, if at all, this plottable's data may be selected. Enforcing this setting on the data selection is done by QCPAbstractPlottable automatically.
\ref QCPDataSelection \b mSelection Holds the current selection state of the plottable's data, i.e. the selected data ranges (\ref QCPDataRange).
*/ /* start of documentation of inline functions */ /*! \fn QCPSelectionDecorator *QCPAbstractPlottable::selectionDecorator() const Provides access to the selection decorator of this plottable. The selection decorator controls how selected data ranges are drawn (e.g. their pen color and fill), see \ref QCPSelectionDecorator for details. If you wish to use an own \ref QCPSelectionDecorator subclass, pass an instance of it to \ref setSelectionDecorator. */ /*! \fn bool QCPAbstractPlottable::selected() const Returns true if there are any data points of the plottable currently selected. Use \ref selection to retrieve the current \ref QCPDataSelection. */ /*! \fn QCPDataSelection QCPAbstractPlottable::selection() const Returns a \ref QCPDataSelection encompassing all the data points that are currently selected on this plottable. \see selected, setSelection, setSelectable */ /*! \fn virtual QCPPlottableInterface1D *QCPAbstractPlottable::interface1D() If this plottable is a one-dimensional plottable, i.e. it implements the \ref QCPPlottableInterface1D, returns the \a this pointer with that type. Otherwise (e.g. in the case of a \ref QCPColorMap) returns zero. You can use this method to gain read access to data coordinates while holding a pointer to the abstract base class only. */ /* end of documentation of inline functions */ /* start of documentation of pure virtual functions */ /*! \fn void QCPAbstractPlottable::drawLegendIcon(QCPPainter *painter, const QRect &rect) const = 0 \internal called by QCPLegend::draw (via QCPPlottableLegendItem::draw) to create a graphical representation of this plottable inside \a rect, next to the plottable name. The passed \a painter has its cliprect set to \a rect, so painting outside of \a rect won't appear outside the legend icon border. */ /*! \fn QCPRange QCPAbstractPlottable::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const = 0 Returns the coordinate range that all data in this plottable span in the key axis dimension. For logarithmic plots, one can set \a inSignDomain to either \ref QCP::sdNegative or \ref QCP::sdPositive in order to restrict the returned range to that sign domain. E.g. when only negative range is wanted, set \a inSignDomain to \ref QCP::sdNegative and all positive points will be ignored for range calculation. For no restriction, just set \a inSignDomain to \ref QCP::sdBoth (default). \a foundRange is an output parameter that indicates whether a range could be found or not. If this is false, you shouldn't use the returned range (e.g. no points in data). Note that \a foundRange is not the same as \ref QCPRange::validRange, since the range returned by this function may have size zero (e.g. when there is only one data point). In this case \a foundRange would return true, but the returned range is not a valid range in terms of \ref QCPRange::validRange. \see rescaleAxes, getValueRange */ /*! \fn QCPRange QCPAbstractPlottable::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const = 0 Returns the coordinate range that the data points in the specified key range (\a inKeyRange) span in the value axis dimension. For logarithmic plots, one can set \a inSignDomain to either \ref QCP::sdNegative or \ref QCP::sdPositive in order to restrict the returned range to that sign domain. E.g. when only negative range is wanted, set \a inSignDomain to \ref QCP::sdNegative and all positive points will be ignored for range calculation. For no restriction, just set \a inSignDomain to \ref QCP::sdBoth (default). \a foundRange is an output parameter that indicates whether a range could be found or not. If this is false, you shouldn't use the returned range (e.g. no points in data). If \a inKeyRange has both lower and upper bound set to zero (is equal to QCPRange()), all data points are considered, without any restriction on the keys. Note that \a foundRange is not the same as \ref QCPRange::validRange, since the range returned by this function may have size zero (e.g. when there is only one data point). In this case \a foundRange would return true, but the returned range is not a valid range in terms of \ref QCPRange::validRange. \see rescaleAxes, getKeyRange */ /* end of documentation of pure virtual functions */ /* start of documentation of signals */ /*! \fn void QCPAbstractPlottable::selectionChanged(bool selected) This signal is emitted when the selection state of this plottable has changed, either by user interaction or by a direct call to \ref setSelection. The parameter \a selected indicates whether there are any points selected or not. \see selectionChanged(const QCPDataSelection &selection) */ /*! \fn void QCPAbstractPlottable::selectionChanged(const QCPDataSelection &selection) This signal is emitted when the selection state of this plottable has changed, either by user interaction or by a direct call to \ref setSelection. The parameter \a selection holds the currently selected data ranges. \see selectionChanged(bool selected) */ /*! \fn void QCPAbstractPlottable::selectableChanged(QCP::SelectionType selectable); This signal is emitted when the selectability of this plottable has changed. \see setSelectable */ /* end of documentation of signals */ /*! Constructs an abstract plottable which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and have perpendicular orientations. If either of these restrictions is violated, a corresponding message is printed to the debug output (qDebug), the construction is not aborted, though. Since QCPAbstractPlottable is an abstract class that defines the basic interface to plottables, it can't be directly instantiated. You probably want one of the subclasses like \ref QCPGraph or \ref QCPCurve instead. */ QCPAbstractPlottable::QCPAbstractPlottable(QCPAxis *keyAxis, QCPAxis *valueAxis) : QCPLayerable(keyAxis->parentPlot(), QString(), keyAxis->axisRect()), mName(), mAntialiasedFill(true), mAntialiasedScatters(true), mPen(Qt::black), mBrush(Qt::NoBrush), mKeyAxis(keyAxis), mValueAxis(valueAxis), - mSelectable(QCP::stWhole), mSelectionDecorator(0) + mSelectable(QCP::stWhole), mSelectionDecorator(nullptr) { if (keyAxis->parentPlot() != valueAxis->parentPlot()) qDebug() << Q_FUNC_INFO << "Parent plot of keyAxis is not the same as that of valueAxis."; if (keyAxis->orientation() == valueAxis->orientation()) qDebug() << Q_FUNC_INFO << "keyAxis and valueAxis must be orthogonal to each other."; mParentPlot->registerPlottable(this); setSelectionDecorator(new QCPSelectionDecorator); } QCPAbstractPlottable::~QCPAbstractPlottable() { if (mSelectionDecorator) { delete mSelectionDecorator; - mSelectionDecorator = 0; + mSelectionDecorator = nullptr; } } /*! The name is the textual representation of this plottable as it is displayed in the legend (\ref QCPLegend). It may contain any UTF-8 characters, including newlines. */ void QCPAbstractPlottable::setName(const QString &name) { mName = name; } /*! Sets whether fills of this plottable are drawn antialiased or not. Note that this setting may be overridden by \ref QCustomPlot::setAntialiasedElements and \ref QCustomPlot::setNotAntialiasedElements. */ void QCPAbstractPlottable::setAntialiasedFill(bool enabled) { mAntialiasedFill = enabled; } /*! Sets whether the scatter symbols of this plottable are drawn antialiased or not. Note that this setting may be overridden by \ref QCustomPlot::setAntialiasedElements and \ref QCustomPlot::setNotAntialiasedElements. */ void QCPAbstractPlottable::setAntialiasedScatters(bool enabled) { mAntialiasedScatters = enabled; } /*! The pen is used to draw basic lines that make up the plottable representation in the plot. For example, the \ref QCPGraph subclass draws its graph lines with this pen. \see setBrush */ void QCPAbstractPlottable::setPen(const QPen &pen) { mPen = pen; } /*! The brush is used to draw basic fills of the plottable representation in the plot. The Fill can be a color, gradient or texture, see the usage of QBrush. For example, the \ref QCPGraph subclass draws the fill under the graph with this brush, when it's not set to Qt::NoBrush. \see setPen */ void QCPAbstractPlottable::setBrush(const QBrush &brush) { mBrush = brush; } /*! The key axis of a plottable can be set to any axis of a QCustomPlot, as long as it is orthogonal to the plottable's value axis. This function performs no checks to make sure this is the case. The typical mathematical choice is to use the x-axis (QCustomPlot::xAxis) as key axis and the y-axis (QCustomPlot::yAxis) as value axis. Normally, the key and value axes are set in the constructor of the plottable (or \ref QCustomPlot::addGraph when working with QCPGraphs through the dedicated graph interface). \see setValueAxis */ void QCPAbstractPlottable::setKeyAxis(QCPAxis *axis) { mKeyAxis = axis; } /*! The value axis of a plottable can be set to any axis of a QCustomPlot, as long as it is orthogonal to the plottable's key axis. This function performs no checks to make sure this is the case. The typical mathematical choice is to use the x-axis (QCustomPlot::xAxis) as key axis and the y-axis (QCustomPlot::yAxis) as value axis. Normally, the key and value axes are set in the constructor of the plottable (or \ref QCustomPlot::addGraph when working with QCPGraphs through the dedicated graph interface). \see setKeyAxis */ void QCPAbstractPlottable::setValueAxis(QCPAxis *axis) { mValueAxis = axis; } /*! Sets which data ranges of this plottable are selected. Selected data ranges are drawn differently (e.g. color) in the plot. This can be controlled via the selection decorator (see \ref selectionDecorator). The entire selection mechanism for plottables is handled automatically when \ref QCustomPlot::setInteractions contains iSelectPlottables. You only need to call this function when you wish to change the selection state programmatically. Using \ref setSelectable you can further specify for each plottable whether and to which granularity it is selectable. If \a selection is not compatible with the current \ref QCP::SelectionType set via \ref setSelectable, the resulting selection will be adjusted accordingly (see \ref QCPDataSelection::enforceType). emits the \ref selectionChanged signal when \a selected is different from the previous selection state. \see setSelectable, selectTest */ void QCPAbstractPlottable::setSelection(QCPDataSelection selection) { selection.enforceType(mSelectable); if (mSelection != selection) { mSelection = selection; emit selectionChanged(selected()); emit selectionChanged(mSelection); } } /*! Use this method to set an own QCPSelectionDecorator (subclass) instance. This allows you to customize the visual representation of selected data ranges further than by using the default QCPSelectionDecorator. The plottable takes ownership of the \a decorator. The currently set decorator can be accessed via \ref selectionDecorator. */ void QCPAbstractPlottable::setSelectionDecorator(QCPSelectionDecorator *decorator) { if (decorator) { if (decorator->registerWithPlottable(this)) { if (mSelectionDecorator) // delete old decorator if necessary delete mSelectionDecorator; mSelectionDecorator = decorator; } } else if (mSelectionDecorator) // just clear decorator { delete mSelectionDecorator; - mSelectionDecorator = 0; + mSelectionDecorator = nullptr; } } /*! Sets whether and to which granularity this plottable can be selected. A selection can happen by clicking on the QCustomPlot surface (When \ref QCustomPlot::setInteractions contains \ref QCP::iSelectPlottables), by dragging a selection rect (When \ref QCustomPlot::setSelectionRectMode is \ref QCP::srmSelect), or programmatically by calling \ref setSelection. \see setSelection, QCP::SelectionType */ void QCPAbstractPlottable::setSelectable(QCP::SelectionType selectable) { if (mSelectable != selectable) { mSelectable = selectable; QCPDataSelection oldSelection = mSelection; mSelection.enforceType(mSelectable); emit selectableChanged(mSelectable); if (mSelection != oldSelection) { emit selectionChanged(selected()); emit selectionChanged(mSelection); } } } /*! Convenience function for transforming a key/value pair to pixels on the QCustomPlot surface, taking the orientations of the axes associated with this plottable into account (e.g. whether key represents x or y). \a key and \a value are transformed to the coodinates in pixels and are written to \a x and \a y. \see pixelsToCoords, QCPAxis::coordToPixel */ void QCPAbstractPlottable::coordsToPixels(double key, double value, double &x, double &y) const { QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; } if (keyAxis->orientation() == Qt::Horizontal) { x = keyAxis->coordToPixel(key); y = valueAxis->coordToPixel(value); } else { y = keyAxis->coordToPixel(key); x = valueAxis->coordToPixel(value); } } /*! \overload Transforms the given \a key and \a value to pixel coordinates and returns them in a QPointF. */ const QPointF QCPAbstractPlottable::coordsToPixels(double key, double value) const { QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPointF(); } if (keyAxis->orientation() == Qt::Horizontal) return QPointF(keyAxis->coordToPixel(key), valueAxis->coordToPixel(value)); else return QPointF(valueAxis->coordToPixel(value), keyAxis->coordToPixel(key)); } /*! Convenience function for transforming a x/y pixel pair on the QCustomPlot surface to plot coordinates, taking the orientations of the axes associated with this plottable into account (e.g. whether key represents x or y). \a x and \a y are transformed to the plot coodinates and are written to \a key and \a value. \see coordsToPixels, QCPAxis::coordToPixel */ void QCPAbstractPlottable::pixelsToCoords(double x, double y, double &key, double &value) const { QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; } if (keyAxis->orientation() == Qt::Horizontal) { key = keyAxis->pixelToCoord(x); value = valueAxis->pixelToCoord(y); } else { key = keyAxis->pixelToCoord(y); value = valueAxis->pixelToCoord(x); } } /*! \overload Returns the pixel input \a pixelPos as plot coordinates \a key and \a value. */ void QCPAbstractPlottable::pixelsToCoords(const QPointF &pixelPos, double &key, double &value) const { pixelsToCoords(pixelPos.x(), pixelPos.y(), key, value); } /*! Rescales the key and value axes associated with this plottable to contain all displayed data, so the whole plottable is visible. If the scaling of an axis is logarithmic, rescaleAxes will make sure not to rescale to an illegal range i.e. a range containing different signs and/or zero. Instead it will stay in the current sign domain and ignore all parts of the plottable that lie outside of that domain. \a onlyEnlarge makes sure the ranges are only expanded, never reduced. So it's possible to show multiple plottables in their entirety by multiple calls to rescaleAxes where the first call has \a onlyEnlarge set to false (the default), and all subsequent set to true. \see rescaleKeyAxis, rescaleValueAxis, QCustomPlot::rescaleAxes, QCPAxis::rescale */ void QCPAbstractPlottable::rescaleAxes(bool onlyEnlarge) const { rescaleKeyAxis(onlyEnlarge); rescaleValueAxis(onlyEnlarge); } /*! Rescales the key axis of the plottable so the whole plottable is visible. See \ref rescaleAxes for detailed behaviour. */ void QCPAbstractPlottable::rescaleKeyAxis(bool onlyEnlarge) const { QCPAxis *keyAxis = mKeyAxis.data(); if (!keyAxis) { qDebug() << Q_FUNC_INFO << "invalid key axis"; return; } QCP::SignDomain signDomain = QCP::sdBoth; if (keyAxis->scaleType() == QCPAxis::stLogarithmic) signDomain = (keyAxis->range().upper < 0 ? QCP::sdNegative : QCP::sdPositive); bool foundRange; QCPRange newRange = getKeyRange(foundRange, signDomain); if (foundRange) { if (onlyEnlarge) newRange.expand(keyAxis->range()); if (!QCPRange::validRange( newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable { double center = (newRange.lower + newRange.upper) * 0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason if (keyAxis->scaleType() == QCPAxis::stLinear) { newRange.lower = center - keyAxis->range().size() / 2.0; newRange.upper = center + keyAxis->range().size() / 2.0; } else // scaleType() == stLogarithmic { newRange.lower = center / qSqrt(keyAxis->range().upper / keyAxis->range().lower); newRange.upper = center * qSqrt(keyAxis->range().upper / keyAxis->range().lower); } } keyAxis->setRange(newRange); } } /*! Rescales the value axis of the plottable so the whole plottable is visible. If \a inKeyRange is set to true, only the data points which are in the currently visible key axis range are considered. Returns true if the axis was actually scaled. This might not be the case if this plottable has an invalid range, e.g. because it has no data points. See \ref rescaleAxes for detailed behaviour. */ void QCPAbstractPlottable::rescaleValueAxis(bool onlyEnlarge, bool inKeyRange) const { QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; } QCP::SignDomain signDomain = QCP::sdBoth; if (valueAxis->scaleType() == QCPAxis::stLogarithmic) signDomain = (valueAxis->range().upper < 0 ? QCP::sdNegative : QCP::sdPositive); bool foundRange; QCPRange newRange = getValueRange(foundRange, signDomain, inKeyRange ? keyAxis->range() : QCPRange()); if (foundRange) { if (onlyEnlarge) newRange.expand(valueAxis->range()); if (!QCPRange::validRange( newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable { double center = (newRange.lower + newRange.upper) * 0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason if (valueAxis->scaleType() == QCPAxis::stLinear) { newRange.lower = center - valueAxis->range().size() / 2.0; newRange.upper = center + valueAxis->range().size() / 2.0; } else // scaleType() == stLogarithmic { newRange.lower = center / qSqrt(valueAxis->range().upper / valueAxis->range().lower); newRange.upper = center * qSqrt(valueAxis->range().upper / valueAxis->range().lower); } } valueAxis->setRange(newRange); } } /*! \overload Adds this plottable to the specified \a legend. Creates a QCPPlottableLegendItem which is inserted into the legend. Returns true on success, i.e. when the legend exists and a legend item associated with this plottable isn't already in the legend. If the plottable needs a more specialized representation in the legend, you can create a corresponding subclass of \ref QCPPlottableLegendItem and add it to the legend manually instead of calling this method. \see removeFromLegend, QCPLegend::addItem */ bool QCPAbstractPlottable::addToLegend(QCPLegend *legend) { if (!legend) { qDebug() << Q_FUNC_INFO << "passed legend is null"; return false; } if (legend->parentPlot() != mParentPlot) { qDebug() << Q_FUNC_INFO << "passed legend isn't in the same QCustomPlot as this plottable"; return false; } if (!legend->hasItemWithPlottable(this)) { legend->addItem(new QCPPlottableLegendItem(legend, this)); return true; } else return false; } /*! \overload Adds this plottable to the legend of the parent QCustomPlot (\ref QCustomPlot::legend). \see removeFromLegend */ bool QCPAbstractPlottable::addToLegend() { if (!mParentPlot || !mParentPlot->legend) return false; else return addToLegend(mParentPlot->legend); } /*! \overload Removes the plottable from the specifed \a legend. This means the \ref QCPPlottableLegendItem that is associated with this plottable is removed. Returns true on success, i.e. if the legend exists and a legend item associated with this plottable was found and removed. \see addToLegend, QCPLegend::removeItem */ bool QCPAbstractPlottable::removeFromLegend(QCPLegend *legend) const { if (!legend) { qDebug() << Q_FUNC_INFO << "passed legend is null"; return false; } if (QCPPlottableLegendItem *lip = legend->itemWithPlottable(this)) return legend->removeItem(lip); else return false; } /*! \overload Removes the plottable from the legend of the parent QCustomPlot. \see addToLegend */ bool QCPAbstractPlottable::removeFromLegend() const { if (!mParentPlot || !mParentPlot->legend) return false; else return removeFromLegend(mParentPlot->legend); } /* inherits documentation from base class */ QRect QCPAbstractPlottable::clipRect() const { if (mKeyAxis && mValueAxis) return mKeyAxis.data()->axisRect()->rect() & mValueAxis.data()->axisRect()->rect(); else return QRect(); } /* inherits documentation from base class */ QCP::Interaction QCPAbstractPlottable::selectionCategory() const { return QCP::iSelectPlottables; } /*! \internal A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter before drawing plottable lines. This is the antialiasing state the painter passed to the \ref draw method is in by default. This function takes into account the local setting of the antialiasing flag as well as the overrides set with \ref QCustomPlot::setAntialiasedElements and \ref QCustomPlot::setNotAntialiasedElements. \seebaseclassmethod \see setAntialiased, applyFillAntialiasingHint, applyScattersAntialiasingHint */ void QCPAbstractPlottable::applyDefaultAntialiasingHint(QCPPainter *painter) const { applyAntialiasingHint(painter, mAntialiased, QCP::aePlottables); } /*! \internal A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter before drawing plottable fills. This function takes into account the local setting of the antialiasing flag as well as the overrides set with \ref QCustomPlot::setAntialiasedElements and \ref QCustomPlot::setNotAntialiasedElements. \see setAntialiased, applyDefaultAntialiasingHint, applyScattersAntialiasingHint */ void QCPAbstractPlottable::applyFillAntialiasingHint(QCPPainter *painter) const { applyAntialiasingHint(painter, mAntialiasedFill, QCP::aeFills); } /*! \internal A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter before drawing plottable scatter points. This function takes into account the local setting of the antialiasing flag as well as the overrides set with \ref QCustomPlot::setAntialiasedElements and \ref QCustomPlot::setNotAntialiasedElements. \see setAntialiased, applyFillAntialiasingHint, applyDefaultAntialiasingHint */ void QCPAbstractPlottable::applyScattersAntialiasingHint(QCPPainter *painter) const { applyAntialiasingHint(painter, mAntialiasedScatters, QCP::aeScatters); } /* inherits documentation from base class */ void QCPAbstractPlottable::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) { Q_UNUSED(event) if (mSelectable != QCP::stNone) { QCPDataSelection newSelection = details.value(); QCPDataSelection selectionBefore = mSelection; if (additive) { if (mSelectable == QCP:: stWhole) // in whole selection mode, we toggle to no selection even if currently unselected point was hit { if (selected()) setSelection(QCPDataSelection()); else setSelection(newSelection); } else // in all other selection modes we toggle selections of homogeneously selected/unselected segments { if (mSelection.contains(newSelection)) // if entire newSelection is already selected, toggle selection setSelection(mSelection - newSelection); else setSelection(mSelection + newSelection); } } else setSelection(newSelection); if (selectionStateChanged) *selectionStateChanged = mSelection != selectionBefore; } } /* inherits documentation from base class */ void QCPAbstractPlottable::deselectEvent(bool *selectionStateChanged) { if (mSelectable != QCP::stNone) { QCPDataSelection selectionBefore = mSelection; setSelection(QCPDataSelection()); if (selectionStateChanged) *selectionStateChanged = mSelection != selectionBefore; } } /* end of 'src/plottable.cpp' */ /* including file 'src/item.cpp', size 49269 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPItemAnchor //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPItemAnchor \brief An anchor of an item to which positions can be attached to. An item (QCPAbstractItem) may have one or more anchors. Unlike QCPItemPosition, an anchor doesn't control anything on its item, but provides a way to tie other items via their positions to the anchor. For example, a QCPItemRect is defined by its positions \a topLeft and \a bottomRight. Additionally it has various anchors like \a top, \a topRight or \a bottomLeft etc. So you can attach the \a start (which is a QCPItemPosition) of a QCPItemLine to one of the anchors by calling QCPItemPosition::setParentAnchor on \a start, passing the wanted anchor of the QCPItemRect. This way the start of the line will now always follow the respective anchor location on the rect item. Note that QCPItemPosition derives from QCPItemAnchor, so every position can also serve as an anchor to other positions. To learn how to provide anchors in your own item subclasses, see the subclassing section of the QCPAbstractItem documentation. */ /* start documentation of inline functions */ /*! \fn virtual QCPItemPosition *QCPItemAnchor::toQCPItemPosition() Returns 0 if this instance is merely a QCPItemAnchor, and a valid pointer of type QCPItemPosition* if it actually is a QCPItemPosition (which is a subclass of QCPItemAnchor). This safe downcast functionality could also be achieved with a dynamic_cast. However, QCustomPlot avoids dynamic_cast to work with projects that don't have RTTI support enabled (e.g. -fno-rtti flag with gcc compiler). */ /* end documentation of inline functions */ /*! Creates a new QCPItemAnchor. You shouldn't create QCPItemAnchor instances directly, even if you want to make a new item subclass. Use \ref QCPAbstractItem::createAnchor instead, as explained in the subclassing section of the QCPAbstractItem documentation. */ QCPItemAnchor::QCPItemAnchor(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString &name, int anchorId) : mName(name), mParentPlot(parentPlot), mParentItem(parentItem), mAnchorId(anchorId) { } QCPItemAnchor::~QCPItemAnchor() { // unregister as parent at children: foreach (QCPItemPosition *child, mChildrenX.toList()) { if (child->parentAnchorX() == this) - child->setParentAnchorX(0); // this acts back on this anchor and child removes itself from mChildrenX + child->setParentAnchorX(nullptr); // this acts back on this anchor and child removes itself from mChildrenX } foreach (QCPItemPosition *child, mChildrenY.toList()) { if (child->parentAnchorY() == this) - child->setParentAnchorY(0); // this acts back on this anchor and child removes itself from mChildrenY + child->setParentAnchorY(nullptr); // this acts back on this anchor and child removes itself from mChildrenY } } /*! Returns the final absolute pixel position of the QCPItemAnchor on the QCustomPlot surface. The pixel information is internally retrieved via QCPAbstractItem::anchorPixelPosition of the parent item, QCPItemAnchor is just an intermediary. */ QPointF QCPItemAnchor::pixelPosition() const { if (mParentItem) { if (mAnchorId > -1) { return mParentItem->anchorPixelPosition(mAnchorId); } else { qDebug() << Q_FUNC_INFO << "no valid anchor id set:" << mAnchorId; return QPointF(); } } else { qDebug() << Q_FUNC_INFO << "no parent item set"; return QPointF(); } } /*! \internal Adds \a pos to the childX list of this anchor, which keeps track of which children use this anchor as parent anchor for the respective coordinate. This is necessary to notify the children prior to destruction of the anchor. Note that this function does not change the parent setting in \a pos. */ void QCPItemAnchor::addChildX(QCPItemPosition *pos) { if (!mChildrenX.contains(pos)) mChildrenX.insert(pos); else qDebug() << Q_FUNC_INFO << "provided pos is child already" << reinterpret_cast(pos); } /*! \internal Removes \a pos from the childX list of this anchor. Note that this function does not change the parent setting in \a pos. */ void QCPItemAnchor::removeChildX(QCPItemPosition *pos) { if (!mChildrenX.remove(pos)) qDebug() << Q_FUNC_INFO << "provided pos isn't child" << reinterpret_cast(pos); } /*! \internal Adds \a pos to the childY list of this anchor, which keeps track of which children use this anchor as parent anchor for the respective coordinate. This is necessary to notify the children prior to destruction of the anchor. Note that this function does not change the parent setting in \a pos. */ void QCPItemAnchor::addChildY(QCPItemPosition *pos) { if (!mChildrenY.contains(pos)) mChildrenY.insert(pos); else qDebug() << Q_FUNC_INFO << "provided pos is child already" << reinterpret_cast(pos); } /*! \internal Removes \a pos from the childY list of this anchor. Note that this function does not change the parent setting in \a pos. */ void QCPItemAnchor::removeChildY(QCPItemPosition *pos) { if (!mChildrenY.remove(pos)) qDebug() << Q_FUNC_INFO << "provided pos isn't child" << reinterpret_cast(pos); } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPItemPosition //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPItemPosition \brief Manages the position of an item. Every item has at least one public QCPItemPosition member pointer which provides ways to position the item on the QCustomPlot surface. Some items have multiple positions, for example QCPItemRect has two: \a topLeft and \a bottomRight. QCPItemPosition has a type (\ref PositionType) that can be set with \ref setType. This type defines how coordinates passed to \ref setCoords are to be interpreted, e.g. as absolute pixel coordinates, as plot coordinates of certain axes, etc. For more advanced plots it is also possible to assign different types per X/Y coordinate of the position (see \ref setTypeX, \ref setTypeY). This way an item could be positioned at a fixed pixel distance from the top in the Y direction, while following a plot coordinate in the X direction. A QCPItemPosition may have a parent QCPItemAnchor, see \ref setParentAnchor. This way you can tie multiple items together. If the QCPItemPosition has a parent, its coordinates (\ref setCoords) are considered to be absolute pixels in the reference frame of the parent anchor, where (0, 0) means directly ontop of the parent anchor. For example, You could attach the \a start position of a QCPItemLine to the \a bottom anchor of a QCPItemText to make the starting point of the line always be centered under the text label, no matter where the text is moved to. For more advanced plots, it is possible to assign different parent anchors per X/Y coordinate of the position, see \ref setParentAnchorX, \ref setParentAnchorY. This way an item could follow another item in the X direction but stay at a fixed position in the Y direction. Or even follow item A in X, and item B in Y. Note that every QCPItemPosition inherits from QCPItemAnchor and thus can itself be used as parent anchor for other positions. To set the apparent pixel position on the QCustomPlot surface directly, use \ref setPixelPosition. This works no matter what type this QCPItemPosition is or what parent-child situation it is in, as \ref setPixelPosition transforms the coordinates appropriately, to make the position appear at the specified pixel values. */ /* start documentation of inline functions */ /*! \fn QCPItemPosition::PositionType *QCPItemPosition::type() const Returns the current position type. If different types were set for X and Y (\ref setTypeX, \ref setTypeY), this method returns the type of the X coordinate. In that case rather use \a typeX() and \a typeY(). \see setType */ /*! \fn QCPItemAnchor *QCPItemPosition::parentAnchor() const Returns the current parent anchor. If different parent anchors were set for X and Y (\ref setParentAnchorX, \ref setParentAnchorY), this method returns the parent anchor of the Y coordinate. In that case rather use \a parentAnchorX() and \a parentAnchorY(). \see setParentAnchor */ /* end documentation of inline functions */ /*! Creates a new QCPItemPosition. You shouldn't create QCPItemPosition instances directly, even if you want to make a new item subclass. Use \ref QCPAbstractItem::createPosition instead, as explained in the subclassing section of the QCPAbstractItem documentation. */ QCPItemPosition::QCPItemPosition(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString &name) : QCPItemAnchor(parentPlot, parentItem, name), mPositionTypeX(ptAbsolute), mPositionTypeY(ptAbsolute), mKey(0), - mValue(0), mParentAnchorX(0), mParentAnchorY(0) + mValue(0), mParentAnchorX(nullptr), mParentAnchorY(nullptr) { } QCPItemPosition::~QCPItemPosition() { // unregister as parent at children: // Note: this is done in ~QCPItemAnchor again, but it's important QCPItemPosition does it itself, because only then // the setParentAnchor(0) call the correct QCPItemPosition::pixelPosition function instead of QCPItemAnchor::pixelPosition foreach (QCPItemPosition *child, mChildrenX.toList()) { if (child->parentAnchorX() == this) - child->setParentAnchorX(0); // this acts back on this anchor and child removes itself from mChildrenX + child->setParentAnchorX(nullptr); // this acts back on this anchor and child removes itself from mChildrenX } foreach (QCPItemPosition *child, mChildrenY.toList()) { if (child->parentAnchorY() == this) - child->setParentAnchorY(0); // this acts back on this anchor and child removes itself from mChildrenY + child->setParentAnchorY(nullptr); // this acts back on this anchor and child removes itself from mChildrenY } // unregister as child in parent: if (mParentAnchorX) mParentAnchorX->removeChildX(this); if (mParentAnchorY) mParentAnchorY->removeChildY(this); } /* can't make this a header inline function, because QPointer breaks with forward declared types, see QTBUG-29588 */ QCPAxisRect *QCPItemPosition::axisRect() const { return mAxisRect.data(); } /*! Sets the type of the position. The type defines how the coordinates passed to \ref setCoords should be handled and how the QCPItemPosition should behave in the plot. The possible values for \a type can be separated in two main categories: \li The position is regarded as a point in plot coordinates. This corresponds to \ref ptPlotCoords and requires two axes that define the plot coordinate system. They can be specified with \ref setAxes. By default, the QCustomPlot's x- and yAxis are used. \li The position is fixed on the QCustomPlot surface, i.e. independent of axis ranges. This corresponds to all other types, i.e. \ref ptAbsolute, \ref ptViewportRatio and \ref ptAxisRectRatio. They differ only in the way the absolute position is described, see the documentation of \ref PositionType for details. For \ref ptAxisRectRatio, note that you can specify the axis rect with \ref setAxisRect. By default this is set to the main axis rect. Note that the position type \ref ptPlotCoords is only available (and sensible) when the position has no parent anchor (\ref setParentAnchor). If the type is changed, the apparent pixel position on the plot is preserved. This means the coordinates as retrieved with coords() and set with \ref setCoords may change in the process. This method sets the type for both X and Y directions. It is also possible to set different types for X and Y, see \ref setTypeX, \ref setTypeY. */ void QCPItemPosition::setType(QCPItemPosition::PositionType type) { setTypeX(type); setTypeY(type); } /*! This method sets the position type of the X coordinate to \a type. For a detailed description of what a position type is, see the documentation of \ref setType. \see setType, setTypeY */ void QCPItemPosition::setTypeX(QCPItemPosition::PositionType type) { if (mPositionTypeX != type) { // if switching from or to coordinate type that isn't valid (e.g. because axes or axis rect // were deleted), don't try to recover the pixelPosition() because it would output a qDebug warning. bool retainPixelPosition = true; if ((mPositionTypeX == ptPlotCoords || type == ptPlotCoords) && (!mKeyAxis || !mValueAxis)) retainPixelPosition = false; if ((mPositionTypeX == ptAxisRectRatio || type == ptAxisRectRatio) && (!mAxisRect)) retainPixelPosition = false; QPointF pixel; if (retainPixelPosition) pixel = pixelPosition(); mPositionTypeX = type; if (retainPixelPosition) setPixelPosition(pixel); } } /*! This method sets the position type of the Y coordinate to \a type. For a detailed description of what a position type is, see the documentation of \ref setType. \see setType, setTypeX */ void QCPItemPosition::setTypeY(QCPItemPosition::PositionType type) { if (mPositionTypeY != type) { // if switching from or to coordinate type that isn't valid (e.g. because axes or axis rect // were deleted), don't try to recover the pixelPosition() because it would output a qDebug warning. bool retainPixelPosition = true; if ((mPositionTypeY == ptPlotCoords || type == ptPlotCoords) && (!mKeyAxis || !mValueAxis)) retainPixelPosition = false; if ((mPositionTypeY == ptAxisRectRatio || type == ptAxisRectRatio) && (!mAxisRect)) retainPixelPosition = false; QPointF pixel; if (retainPixelPosition) pixel = pixelPosition(); mPositionTypeY = type; if (retainPixelPosition) setPixelPosition(pixel); } } /*! Sets the parent of this QCPItemPosition to \a parentAnchor. This means the position will now follow any position changes of the anchor. The local coordinate system of positions with a parent anchor always is absolute pixels, with (0, 0) being exactly on top of the parent anchor. (Hence the type shouldn't be set to \ref ptPlotCoords for positions with parent anchors.) if \a keepPixelPosition is true, the current pixel position of the QCPItemPosition is preserved during reparenting. If it's set to false, the coordinates are set to (0, 0), i.e. the position will be exactly on top of the parent anchor. To remove this QCPItemPosition from any parent anchor, set \a parentAnchor to 0. If the QCPItemPosition previously had no parent and the type is \ref ptPlotCoords, the type is set to \ref ptAbsolute, to keep the position in a valid state. This method sets the parent anchor for both X and Y directions. It is also possible to set different parents for X and Y, see \ref setParentAnchorX, \ref setParentAnchorY. */ bool QCPItemPosition::setParentAnchor(QCPItemAnchor *parentAnchor, bool keepPixelPosition) { bool successX = setParentAnchorX(parentAnchor, keepPixelPosition); bool successY = setParentAnchorY(parentAnchor, keepPixelPosition); return successX && successY; } /*! This method sets the parent anchor of the X coordinate to \a parentAnchor. For a detailed description of what a parent anchor is, see the documentation of \ref setParentAnchor. \see setParentAnchor, setParentAnchorY */ bool QCPItemPosition::setParentAnchorX(QCPItemAnchor *parentAnchor, bool keepPixelPosition) { // make sure self is not assigned as parent: if (parentAnchor == this) { qDebug() << Q_FUNC_INFO << "can't set self as parent anchor" << reinterpret_cast(parentAnchor); return false; } // make sure no recursive parent-child-relationships are created: QCPItemAnchor *currentParent = parentAnchor; while (currentParent) { if (QCPItemPosition *currentParentPos = currentParent->toQCPItemPosition()) { // is a QCPItemPosition, might have further parent, so keep iterating if (currentParentPos == this) { qDebug() << Q_FUNC_INFO << "can't create recursive parent-child-relationship" << reinterpret_cast(parentAnchor); return false; } currentParent = currentParentPos->parentAnchorX(); } else { // is a QCPItemAnchor, can't have further parent. Now make sure the parent items aren't the // same, to prevent a position being child of an anchor which itself depends on the position, // because they're both on the same item: if (currentParent->mParentItem == mParentItem) { qDebug() << Q_FUNC_INFO << "can't set parent to be an anchor which itself depends on this position" << reinterpret_cast(parentAnchor); return false; } break; } } // if previously no parent set and PosType is still ptPlotCoords, set to ptAbsolute: if (!mParentAnchorX && mPositionTypeX == ptPlotCoords) setTypeX(ptAbsolute); // save pixel position: QPointF pixelP; if (keepPixelPosition) pixelP = pixelPosition(); // unregister at current parent anchor: if (mParentAnchorX) mParentAnchorX->removeChildX(this); // register at new parent anchor: if (parentAnchor) parentAnchor->addChildX(this); mParentAnchorX = parentAnchor; // restore pixel position under new parent: if (keepPixelPosition) setPixelPosition(pixelP); else setCoords(0, coords().y()); return true; } /*! This method sets the parent anchor of the Y coordinate to \a parentAnchor. For a detailed description of what a parent anchor is, see the documentation of \ref setParentAnchor. \see setParentAnchor, setParentAnchorX */ bool QCPItemPosition::setParentAnchorY(QCPItemAnchor *parentAnchor, bool keepPixelPosition) { // make sure self is not assigned as parent: if (parentAnchor == this) { qDebug() << Q_FUNC_INFO << "can't set self as parent anchor" << reinterpret_cast(parentAnchor); return false; } // make sure no recursive parent-child-relationships are created: QCPItemAnchor *currentParent = parentAnchor; while (currentParent) { if (QCPItemPosition *currentParentPos = currentParent->toQCPItemPosition()) { // is a QCPItemPosition, might have further parent, so keep iterating if (currentParentPos == this) { qDebug() << Q_FUNC_INFO << "can't create recursive parent-child-relationship" << reinterpret_cast(parentAnchor); return false; } currentParent = currentParentPos->parentAnchorY(); } else { // is a QCPItemAnchor, can't have further parent. Now make sure the parent items aren't the // same, to prevent a position being child of an anchor which itself depends on the position, // because they're both on the same item: if (currentParent->mParentItem == mParentItem) { qDebug() << Q_FUNC_INFO << "can't set parent to be an anchor which itself depends on this position" << reinterpret_cast(parentAnchor); return false; } break; } } // if previously no parent set and PosType is still ptPlotCoords, set to ptAbsolute: if (!mParentAnchorY && mPositionTypeY == ptPlotCoords) setTypeY(ptAbsolute); // save pixel position: QPointF pixelP; if (keepPixelPosition) pixelP = pixelPosition(); // unregister at current parent anchor: if (mParentAnchorY) mParentAnchorY->removeChildY(this); // register at new parent anchor: if (parentAnchor) parentAnchor->addChildY(this); mParentAnchorY = parentAnchor; // restore pixel position under new parent: if (keepPixelPosition) setPixelPosition(pixelP); else setCoords(coords().x(), 0); return true; } /*! Sets the coordinates of this QCPItemPosition. What the coordinates mean, is defined by the type (\ref setType, \ref setTypeX, \ref setTypeY). For example, if the type is \ref ptAbsolute, \a key and \a value mean the x and y pixel position on the QCustomPlot surface. In that case the origin (0, 0) is in the top left corner of the QCustomPlot viewport. If the type is \ref ptPlotCoords, \a key and \a value mean a point in the plot coordinate system defined by the axes set by \ref setAxes. By default those are the QCustomPlot's xAxis and yAxis. See the documentation of \ref setType for other available coordinate types and their meaning. If different types were configured for X and Y (\ref setTypeX, \ref setTypeY), \a key and \a value must also be provided in the different coordinate systems. Here, the X type refers to \a key, and the Y type refers to \a value. \see setPixelPosition */ void QCPItemPosition::setCoords(double key, double value) { mKey = key; mValue = value; } /*! \overload Sets the coordinates as a QPointF \a pos where pos.x has the meaning of \a key and pos.y the meaning of \a value of the \ref setCoords(double key, double value) method. */ void QCPItemPosition::setCoords(const QPointF &pos) { setCoords(pos.x(), pos.y()); } /*! Returns the final absolute pixel position of the QCPItemPosition on the QCustomPlot surface. It includes all effects of type (\ref setType) and possible parent anchors (\ref setParentAnchor). \see setPixelPosition */ QPointF QCPItemPosition::pixelPosition() const { QPointF result; // determine X: switch (mPositionTypeX) { case ptAbsolute: { result.rx() = mKey; if (mParentAnchorX) result.rx() += mParentAnchorX->pixelPosition().x(); break; } case ptViewportRatio: { result.rx() = mKey * mParentPlot->viewport().width(); if (mParentAnchorX) result.rx() += mParentAnchorX->pixelPosition().x(); else result.rx() += mParentPlot->viewport().left(); break; } case ptAxisRectRatio: { if (mAxisRect) { result.rx() = mKey * mAxisRect.data()->width(); if (mParentAnchorX) result.rx() += mParentAnchorX->pixelPosition().x(); else result.rx() += mAxisRect.data()->left(); } else qDebug() << Q_FUNC_INFO << "Item position type x is ptAxisRectRatio, but no axis rect was defined"; break; } case ptPlotCoords: { if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Horizontal) result.rx() = mKeyAxis.data()->coordToPixel(mKey); else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Horizontal) result.rx() = mValueAxis.data()->coordToPixel(mValue); else qDebug() << Q_FUNC_INFO << "Item position type x is ptPlotCoords, but no axes were defined"; break; } } // determine Y: switch (mPositionTypeY) { case ptAbsolute: { result.ry() = mValue; if (mParentAnchorY) result.ry() += mParentAnchorY->pixelPosition().y(); break; } case ptViewportRatio: { result.ry() = mValue * mParentPlot->viewport().height(); if (mParentAnchorY) result.ry() += mParentAnchorY->pixelPosition().y(); else result.ry() += mParentPlot->viewport().top(); break; } case ptAxisRectRatio: { if (mAxisRect) { result.ry() = mValue * mAxisRect.data()->height(); if (mParentAnchorY) result.ry() += mParentAnchorY->pixelPosition().y(); else result.ry() += mAxisRect.data()->top(); } else qDebug() << Q_FUNC_INFO << "Item position type y is ptAxisRectRatio, but no axis rect was defined"; break; } case ptPlotCoords: { if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Vertical) result.ry() = mKeyAxis.data()->coordToPixel(mKey); else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Vertical) result.ry() = mValueAxis.data()->coordToPixel(mValue); else qDebug() << Q_FUNC_INFO << "Item position type y is ptPlotCoords, but no axes were defined"; break; } } return result; } /*! When \ref setType is \ref ptPlotCoords, this function may be used to specify the axes the coordinates set with \ref setCoords relate to. By default they are set to the initial xAxis and yAxis of the QCustomPlot. */ void QCPItemPosition::setAxes(QCPAxis *keyAxis, QCPAxis *valueAxis) { mKeyAxis = keyAxis; mValueAxis = valueAxis; } /*! When \ref setType is \ref ptAxisRectRatio, this function may be used to specify the axis rect the coordinates set with \ref setCoords relate to. By default this is set to the main axis rect of the QCustomPlot. */ void QCPItemPosition::setAxisRect(QCPAxisRect *axisRect) { mAxisRect = axisRect; } /*! Sets the apparent pixel position. This works no matter what type (\ref setType) this QCPItemPosition is or what parent-child situation it is in, as coordinates are transformed appropriately, to make the position finally appear at the specified pixel values. Only if the type is \ref ptAbsolute and no parent anchor is set, this function's effect is identical to that of \ref setCoords. \see pixelPosition, setCoords */ void QCPItemPosition::setPixelPosition(const QPointF &pixelPosition) { double x = pixelPosition.x(); double y = pixelPosition.y(); switch (mPositionTypeX) { case ptAbsolute: { if (mParentAnchorX) x -= mParentAnchorX->pixelPosition().x(); break; } case ptViewportRatio: { if (mParentAnchorX) x -= mParentAnchorX->pixelPosition().x(); else x -= mParentPlot->viewport().left(); x /= (double)mParentPlot->viewport().width(); break; } case ptAxisRectRatio: { if (mAxisRect) { if (mParentAnchorX) x -= mParentAnchorX->pixelPosition().x(); else x -= mAxisRect.data()->left(); x /= (double)mAxisRect.data()->width(); } else qDebug() << Q_FUNC_INFO << "Item position type x is ptAxisRectRatio, but no axis rect was defined"; break; } case ptPlotCoords: { if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Horizontal) x = mKeyAxis.data()->pixelToCoord(x); else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Horizontal) y = mValueAxis.data()->pixelToCoord(x); else qDebug() << Q_FUNC_INFO << "Item position type x is ptPlotCoords, but no axes were defined"; break; } } switch (mPositionTypeY) { case ptAbsolute: { if (mParentAnchorY) y -= mParentAnchorY->pixelPosition().y(); break; } case ptViewportRatio: { if (mParentAnchorY) y -= mParentAnchorY->pixelPosition().y(); else y -= mParentPlot->viewport().top(); y /= (double)mParentPlot->viewport().height(); break; } case ptAxisRectRatio: { if (mAxisRect) { if (mParentAnchorY) y -= mParentAnchorY->pixelPosition().y(); else y -= mAxisRect.data()->top(); y /= (double)mAxisRect.data()->height(); } else qDebug() << Q_FUNC_INFO << "Item position type y is ptAxisRectRatio, but no axis rect was defined"; break; } case ptPlotCoords: { if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Vertical) x = mKeyAxis.data()->pixelToCoord(y); else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Vertical) y = mValueAxis.data()->pixelToCoord(y); else qDebug() << Q_FUNC_INFO << "Item position type y is ptPlotCoords, but no axes were defined"; break; } } setCoords(x, y); } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAbstractItem //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPAbstractItem \brief The abstract base class for all items in a plot. In QCustomPlot, items are supplemental graphical elements that are neither plottables (QCPAbstractPlottable) nor axes (QCPAxis). While plottables are always tied to two axes and thus plot coordinates, items can also be placed in absolute coordinates independent of any axes. Each specific item has at least one QCPItemPosition member which controls the positioning. Some items are defined by more than one coordinate and thus have two or more QCPItemPosition members (For example, QCPItemRect has \a topLeft and \a bottomRight). This abstract base class defines a very basic interface like visibility and clipping. Since this class is abstract, it can't be instantiated. Use one of the subclasses or create a subclass yourself to create new items. The built-in items are:
QCPItemLineA line defined by a start and an end point. May have different ending styles on each side (e.g. arrows).
QCPItemStraightLineA straight line defined by a start and a direction point. Unlike QCPItemLine, the straight line is infinitely long and has no endings.
QCPItemCurveA curve defined by start, end and two intermediate control points. May have different ending styles on each side (e.g. arrows).
QCPItemRectA rectangle
QCPItemEllipseAn ellipse
QCPItemPixmapAn arbitrary pixmap
QCPItemTextA text label
QCPItemBracketA bracket which may be used to reference/highlight certain parts in the plot.
QCPItemTracerAn item that can be attached to a QCPGraph and sticks to its data points, given a key coordinate.
\section items-clipping Clipping Items are by default clipped to the main axis rect (they are only visible inside the axis rect). To make an item visible outside that axis rect, disable clipping via \ref setClipToAxisRect "setClipToAxisRect(false)". On the other hand if you want the item to be clipped to a different axis rect, specify it via \ref setClipAxisRect. This clipAxisRect property of an item is only used for clipping behaviour, and in principle is independent of the coordinate axes the item might be tied to via its position members (\ref QCPItemPosition::setAxes). However, it is common that the axis rect for clipping also contains the axes used for the item positions. \section items-using Using items First you instantiate the item you want to use and add it to the plot: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-1 by default, the positions of the item are bound to the x- and y-Axis of the plot. So we can just set the plot coordinates where the line should start/end: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-2 If we don't want the line to be positioned in plot coordinates but a different coordinate system, e.g. absolute pixel positions on the QCustomPlot surface, we need to change the position type like this: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-3 Then we can set the coordinates, this time in pixels: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-4 and make the line visible on the entire QCustomPlot, by disabling clipping to the axis rect: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-5 For more advanced plots, it is even possible to set different types and parent anchors per X/Y coordinate of an item position, using for example \ref QCPItemPosition::setTypeX or \ref QCPItemPosition::setParentAnchorX. For details, see the documentation of \ref QCPItemPosition. \section items-subclassing Creating own items To create an own item, you implement a subclass of QCPAbstractItem. These are the pure virtual functions, you must implement: \li \ref selectTest \li \ref draw See the documentation of those functions for what they need to do. \subsection items-positioning Allowing the item to be positioned As mentioned, item positions are represented by QCPItemPosition members. Let's assume the new item shall have only one point as its position (as opposed to two like a rect or multiple like a polygon). You then add a public member of type QCPItemPosition like so: \code QCPItemPosition * const myPosition;\endcode the const makes sure the pointer itself can't be modified from the user of your new item (the QCPItemPosition instance it points to, can be modified, of course). The initialization of this pointer is made easy with the \ref createPosition function. Just assign the return value of this function to each QCPItemPosition in the constructor of your item. \ref createPosition takes a string which is the name of the position, typically this is identical to the variable name. For example, the constructor of QCPItemExample could look like this: \code QCPItemExample::QCPItemExample(QCustomPlot *parentPlot) : QCPAbstractItem(parentPlot), myPosition(createPosition("myPosition")) { // other constructor code } \endcode \subsection items-drawing The draw function To give your item a visual representation, reimplement the \ref draw function and use the passed QCPPainter to draw the item. You can retrieve the item position in pixel coordinates from the position member(s) via \ref QCPItemPosition::pixelPosition. To optimize performance you should calculate a bounding rect first (don't forget to take the pen width into account), check whether it intersects the \ref clipRect, and only draw the item at all if this is the case. \subsection items-selection The selectTest function Your implementation of the \ref selectTest function may use the helpers \ref QCPVector2D::distanceSquaredToLine and \ref rectDistance. With these, the implementation of the selection test becomes significantly simpler for most items. See the documentation of \ref selectTest for what the function parameters mean and what the function should return. \subsection anchors Providing anchors Providing anchors (QCPItemAnchor) starts off like adding a position. First you create a public member, e.g. \code QCPItemAnchor * const bottom;\endcode and create it in the constructor with the \ref createAnchor function, assigning it a name and an anchor id (an integer enumerating all anchors on the item, you may create an own enum for this). Since anchors can be placed anywhere, relative to the item's position(s), your item needs to provide the position of every anchor with the reimplementation of the \ref anchorPixelPosition(int anchorId) function. In essence the QCPItemAnchor is merely an intermediary that itself asks your item for the pixel position when anything attached to the anchor needs to know the coordinates. */ /* start of documentation of inline functions */ /*! \fn QList QCPAbstractItem::positions() const Returns all positions of the item in a list. \see anchors, position */ /*! \fn QList QCPAbstractItem::anchors() const Returns all anchors of the item in a list. Note that since a position (QCPItemPosition) is always also an anchor, the list will also contain the positions of this item. \see positions, anchor */ /* end of documentation of inline functions */ /* start documentation of pure virtual functions */ /*! \fn void QCPAbstractItem::draw(QCPPainter *painter) = 0 \internal Draws this item with the provided \a painter. The cliprect of the provided painter is set to the rect returned by \ref clipRect before this function is called. The clipRect depends on the clipping settings defined by \ref setClipToAxisRect and \ref setClipAxisRect. */ /* end documentation of pure virtual functions */ /* start documentation of signals */ /*! \fn void QCPAbstractItem::selectionChanged(bool selected) This signal is emitted when the selection state of this item has changed, either by user interaction or by a direct call to \ref setSelected. */ /* end documentation of signals */ /*! Base class constructor which initializes base class members. */ QCPAbstractItem::QCPAbstractItem(QCustomPlot *parentPlot) : QCPLayerable(parentPlot), mClipToAxisRect(false), mSelectable(true), mSelected(false) { parentPlot->registerItem(this); QList rects = parentPlot->axisRects(); if (rects.size() > 0) { setClipToAxisRect(true); setClipAxisRect(rects.first()); } } QCPAbstractItem::~QCPAbstractItem() { // don't delete mPositions because every position is also an anchor and thus in mAnchors qDeleteAll(mAnchors); } /* can't make this a header inline function, because QPointer breaks with forward declared types, see QTBUG-29588 */ QCPAxisRect *QCPAbstractItem::clipAxisRect() const { return mClipAxisRect.data(); } /*! Sets whether the item shall be clipped to an axis rect or whether it shall be visible on the entire QCustomPlot. The axis rect can be set with \ref setClipAxisRect. \see setClipAxisRect */ void QCPAbstractItem::setClipToAxisRect(bool clip) { mClipToAxisRect = clip; if (mClipToAxisRect) setParentLayerable(mClipAxisRect.data()); } /*! Sets the clip axis rect. It defines the rect that will be used to clip the item when \ref setClipToAxisRect is set to true. \see setClipToAxisRect */ void QCPAbstractItem::setClipAxisRect(QCPAxisRect *rect) { mClipAxisRect = rect; if (mClipToAxisRect) setParentLayerable(mClipAxisRect.data()); } /*! Sets whether the user can (de-)select this item by clicking on the QCustomPlot surface. (When \ref QCustomPlot::setInteractions contains QCustomPlot::iSelectItems.) However, even when \a selectable was set to false, it is possible to set the selection manually, by calling \ref setSelected. \see QCustomPlot::setInteractions, setSelected */ void QCPAbstractItem::setSelectable(bool selectable) { if (mSelectable != selectable) { mSelectable = selectable; emit selectableChanged(mSelectable); } } /*! Sets whether this item is selected or not. When selected, it might use a different visual appearance (e.g. pen and brush), this depends on the specific item though. The entire selection mechanism for items is handled automatically when \ref QCustomPlot::setInteractions contains QCustomPlot::iSelectItems. You only need to call this function when you wish to change the selection state manually. This function can change the selection state even when \ref setSelectable was set to false. emits the \ref selectionChanged signal when \a selected is different from the previous selection state. \see setSelectable, selectTest */ void QCPAbstractItem::setSelected(bool selected) { if (mSelected != selected) { mSelected = selected; emit selectionChanged(mSelected); } } /*! Returns the QCPItemPosition with the specified \a name. If this item doesn't have a position by that name, returns 0. This function provides an alternative way to access item positions. Normally, you access positions direcly by their member pointers (which typically have the same variable name as \a name). \see positions, anchor */ QCPItemPosition *QCPAbstractItem::position(const QString &name) const { for (int i = 0; i < mPositions.size(); ++i) { if (mPositions.at(i)->name() == name) return mPositions.at(i); } qDebug() << Q_FUNC_INFO << "position with name not found:" << name; - return 0; + return nullptr; } /*! Returns the QCPItemAnchor with the specified \a name. If this item doesn't have an anchor by that name, returns 0. This function provides an alternative way to access item anchors. Normally, you access anchors direcly by their member pointers (which typically have the same variable name as \a name). \see anchors, position */ QCPItemAnchor *QCPAbstractItem::anchor(const QString &name) const { for (int i = 0; i < mAnchors.size(); ++i) { if (mAnchors.at(i)->name() == name) return mAnchors.at(i); } qDebug() << Q_FUNC_INFO << "anchor with name not found:" << name; - return 0; + return nullptr; } /*! Returns whether this item has an anchor with the specified \a name. Note that you can check for positions with this function, too. This is because every position is also an anchor (QCPItemPosition inherits from QCPItemAnchor). \see anchor, position */ bool QCPAbstractItem::hasAnchor(const QString &name) const { for (int i = 0; i < mAnchors.size(); ++i) { if (mAnchors.at(i)->name() == name) return true; } return false; } /*! \internal Returns the rect the visual representation of this item is clipped to. This depends on the current setting of \ref setClipToAxisRect as well as the axis rect set with \ref setClipAxisRect. If the item is not clipped to an axis rect, QCustomPlot's viewport rect is returned. \see draw */ QRect QCPAbstractItem::clipRect() const { if (mClipToAxisRect && mClipAxisRect) return mClipAxisRect.data()->rect(); else return mParentPlot->viewport(); } /*! \internal A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter before drawing item lines. This is the antialiasing state the painter passed to the \ref draw method is in by default. This function takes into account the local setting of the antialiasing flag as well as the overrides set with \ref QCustomPlot::setAntialiasedElements and \ref QCustomPlot::setNotAntialiasedElements. \see setAntialiased */ void QCPAbstractItem::applyDefaultAntialiasingHint(QCPPainter *painter) const { applyAntialiasingHint(painter, mAntialiased, QCP::aeItems); } /*! \internal A convenience function which returns the selectTest value for a specified \a rect and a specified click position \a pos. \a filledRect defines whether a click inside the rect should also be considered a hit or whether only the rect border is sensitive to hits. This function may be used to help with the implementation of the \ref selectTest function for specific items. For example, if your item consists of four rects, call this function four times, once for each rect, in your \ref selectTest reimplementation. Finally, return the minimum (non -1) of all four returned values. */ double QCPAbstractItem::rectDistance(const QRectF &rect, const QPointF &pos, bool filledRect) const { double result = -1; // distance to border: QList lines; lines << QLineF(rect.topLeft(), rect.topRight()) << QLineF(rect.bottomLeft(), rect.bottomRight()) << QLineF(rect.topLeft(), rect.bottomLeft()) << QLineF(rect.topRight(), rect.bottomRight()); double minDistSqr = std::numeric_limits::max(); for (int i = 0; i < lines.size(); ++i) { double distSqr = QCPVector2D(pos).distanceSquaredToLine(lines.at(i).p1(), lines.at(i).p2()); if (distSqr < minDistSqr) minDistSqr = distSqr; } result = qSqrt(minDistSqr); // filled rect, allow click inside to count as hit: if (filledRect && result > mParentPlot->selectionTolerance() * 0.99) { if (rect.contains(pos)) result = mParentPlot->selectionTolerance() * 0.99; } return result; } /*! \internal Returns the pixel position of the anchor with Id \a anchorId. This function must be reimplemented in item subclasses if they want to provide anchors (QCPItemAnchor). For example, if the item has two anchors with id 0 and 1, this function takes one of these anchor ids and returns the respective pixel points of the specified anchor. \see createAnchor */ QPointF QCPAbstractItem::anchorPixelPosition(int anchorId) const { qDebug() << Q_FUNC_INFO << "called on item which shouldn't have any anchors (this method not reimplemented). anchorId" << anchorId; return QPointF(); } /*! \internal Creates a QCPItemPosition, registers it with this item and returns a pointer to it. The specified \a name must be a unique string that is usually identical to the variable name of the position member (This is needed to provide the name-based \ref position access to positions). Don't delete positions created by this function manually, as the item will take care of it. Use this function in the constructor (initialization list) of the specific item subclass to create each position member. Don't create QCPItemPositions with \b new yourself, because they won't be registered with the item properly. \see createAnchor */ QCPItemPosition *QCPAbstractItem::createPosition(const QString &name) { if (hasAnchor(name)) qDebug() << Q_FUNC_INFO << "anchor/position with name exists already:" << name; QCPItemPosition *newPosition = new QCPItemPosition(mParentPlot, this, name); mPositions.append(newPosition); mAnchors.append(newPosition); // every position is also an anchor newPosition->setAxes(mParentPlot->xAxis, mParentPlot->yAxis); newPosition->setType(QCPItemPosition::ptPlotCoords); if (mParentPlot->axisRect()) newPosition->setAxisRect(mParentPlot->axisRect()); newPosition->setCoords(0, 0); return newPosition; } /*! \internal Creates a QCPItemAnchor, registers it with this item and returns a pointer to it. The specified \a name must be a unique string that is usually identical to the variable name of the anchor member (This is needed to provide the name based \ref anchor access to anchors). The \a anchorId must be a number identifying the created anchor. It is recommended to create an enum (e.g. "AnchorIndex") for this on each item that uses anchors. This id is used by the anchor to identify itself when it calls QCPAbstractItem::anchorPixelPosition. That function then returns the correct pixel coordinates for the passed anchor id. Don't delete anchors created by this function manually, as the item will take care of it. Use this function in the constructor (initialization list) of the specific item subclass to create each anchor member. Don't create QCPItemAnchors with \b new yourself, because then they won't be registered with the item properly. \see createPosition */ QCPItemAnchor *QCPAbstractItem::createAnchor(const QString &name, int anchorId) { if (hasAnchor(name)) qDebug() << Q_FUNC_INFO << "anchor/position with name exists already:" << name; QCPItemAnchor *newAnchor = new QCPItemAnchor(mParentPlot, this, name, anchorId); mAnchors.append(newAnchor); return newAnchor; } /* inherits documentation from base class */ void QCPAbstractItem::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) { Q_UNUSED(event) Q_UNUSED(details) if (mSelectable) { bool selBefore = mSelected; setSelected(additive ? !mSelected : true); if (selectionStateChanged) *selectionStateChanged = mSelected != selBefore; } } /* inherits documentation from base class */ void QCPAbstractItem::deselectEvent(bool *selectionStateChanged) { if (mSelectable) { bool selBefore = mSelected; setSelected(false); if (selectionStateChanged) *selectionStateChanged = mSelected != selBefore; } } /* inherits documentation from base class */ QCP::Interaction QCPAbstractItem::selectionCategory() const { return QCP::iSelectItems; } /* end of 'src/item.cpp' */ /* including file 'src/core.cpp', size 124243 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCustomPlot //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCustomPlot \brief The central class of the library. This is the QWidget which displays the plot and interacts with the user. For tutorials on how to use QCustomPlot, see the website\n http://www.qcustomplot.com/ */ /* start of documentation of inline functions */ /*! \fn QCPSelectionRect *QCustomPlot::selectionRect() const Allows access to the currently used QCPSelectionRect instance (or subclass thereof), that is used to handle and draw selection rect interactions (see \ref setSelectionRectMode). \see setSelectionRect */ /*! \fn QCPLayoutGrid *QCustomPlot::plotLayout() const Returns the top level layout of this QCustomPlot instance. It is a \ref QCPLayoutGrid, initially containing just one cell with the main QCPAxisRect inside. */ /* end of documentation of inline functions */ /* start of documentation of signals */ /*! \fn void QCustomPlot::mouseDoubleClick(QMouseEvent *event) This signal is emitted when the QCustomPlot receives a mouse double click event. */ /*! \fn void QCustomPlot::mousePress(QMouseEvent *event) This signal is emitted when the QCustomPlot receives a mouse press event. It is emitted before QCustomPlot handles any other mechanism like range dragging. So a slot connected to this signal can still influence the behaviour e.g. with \ref QCPAxisRect::setRangeDrag or \ref QCPAxisRect::setRangeDragAxes. */ /*! \fn void QCustomPlot::mouseMove(QMouseEvent *event) This signal is emitted when the QCustomPlot receives a mouse move event. It is emitted before QCustomPlot handles any other mechanism like range dragging. So a slot connected to this signal can still influence the behaviour e.g. with \ref QCPAxisRect::setRangeDrag or \ref QCPAxisRect::setRangeDragAxes. \warning It is discouraged to change the drag-axes with \ref QCPAxisRect::setRangeDragAxes here, because the dragging starting point was saved the moment the mouse was pressed. Thus it only has a meaning for the range drag axes that were set at that moment. If you want to change the drag axes, consider doing this in the \ref mousePress signal instead. */ /*! \fn void QCustomPlot::mouseRelease(QMouseEvent *event) This signal is emitted when the QCustomPlot receives a mouse release event. It is emitted before QCustomPlot handles any other mechanisms like object selection. So a slot connected to this signal can still influence the behaviour e.g. with \ref setInteractions or \ref QCPAbstractPlottable::setSelectable. */ /*! \fn void QCustomPlot::mouseWheel(QMouseEvent *event) This signal is emitted when the QCustomPlot receives a mouse wheel event. It is emitted before QCustomPlot handles any other mechanisms like range zooming. So a slot connected to this signal can still influence the behaviour e.g. with \ref QCPAxisRect::setRangeZoom, \ref QCPAxisRect::setRangeZoomAxes or \ref QCPAxisRect::setRangeZoomFactor. */ /*! \fn void QCustomPlot::plottableClick(QCPAbstractPlottable *plottable, int dataIndex, QMouseEvent *event) This signal is emitted when a plottable is clicked. \a event is the mouse event that caused the click and \a plottable is the plottable that received the click. The parameter \a dataIndex indicates the data point that was closest to the click position. \see plottableDoubleClick */ /*! \fn void QCustomPlot::plottableDoubleClick(QCPAbstractPlottable *plottable, int dataIndex, QMouseEvent *event) This signal is emitted when a plottable is double clicked. \a event is the mouse event that caused the click and \a plottable is the plottable that received the click. The parameter \a dataIndex indicates the data point that was closest to the click position. \see plottableClick */ /*! \fn void QCustomPlot::itemClick(QCPAbstractItem *item, QMouseEvent *event) This signal is emitted when an item is clicked. \a event is the mouse event that caused the click and \a item is the item that received the click. \see itemDoubleClick */ /*! \fn void QCustomPlot::itemDoubleClick(QCPAbstractItem *item, QMouseEvent *event) This signal is emitted when an item is double clicked. \a event is the mouse event that caused the click and \a item is the item that received the click. \see itemClick */ /*! \fn void QCustomPlot::axisClick(QCPAxis *axis, QCPAxis::SelectablePart part, QMouseEvent *event) This signal is emitted when an axis is clicked. \a event is the mouse event that caused the click, \a axis is the axis that received the click and \a part indicates the part of the axis that was clicked. \see axisDoubleClick */ /*! \fn void QCustomPlot::axisDoubleClick(QCPAxis *axis, QCPAxis::SelectablePart part, QMouseEvent *event) This signal is emitted when an axis is double clicked. \a event is the mouse event that caused the click, \a axis is the axis that received the click and \a part indicates the part of the axis that was clicked. \see axisClick */ /*! \fn void QCustomPlot::legendClick(QCPLegend *legend, QCPAbstractLegendItem *item, QMouseEvent *event) This signal is emitted when a legend (item) is clicked. \a event is the mouse event that caused the click, \a legend is the legend that received the click and \a item is the legend item that received the click. If only the legend and no item is clicked, \a item is 0. This happens for a click inside the legend padding or the space between two items. \see legendDoubleClick */ /*! \fn void QCustomPlot::legendDoubleClick(QCPLegend *legend, QCPAbstractLegendItem *item, QMouseEvent *event) This signal is emitted when a legend (item) is double clicked. \a event is the mouse event that caused the click, \a legend is the legend that received the click and \a item is the legend item that received the click. If only the legend and no item is clicked, \a item is 0. This happens for a click inside the legend padding or the space between two items. \see legendClick */ /*! \fn void QCustomPlot::selectionChangedByUser() This signal is emitted after the user has changed the selection in the QCustomPlot, e.g. by clicking. It is not emitted when the selection state of an object has changed programmatically by a direct call to setSelected()/setSelection() on an object or by calling \ref deselectAll. In addition to this signal, selectable objects also provide individual signals, for example \ref QCPAxis::selectionChanged or \ref QCPAbstractPlottable::selectionChanged. Note that those signals are emitted even if the selection state is changed programmatically. See the documentation of \ref setInteractions for details about the selection mechanism. \see selectedPlottables, selectedGraphs, selectedItems, selectedAxes, selectedLegends */ /*! \fn void QCustomPlot::beforeReplot() This signal is emitted immediately before a replot takes place (caused by a call to the slot \ref replot). It is safe to mutually connect the replot slot with this signal on two QCustomPlots to make them replot synchronously, it won't cause an infinite recursion. \see replot, afterReplot */ /*! \fn void QCustomPlot::afterReplot() This signal is emitted immediately after a replot has taken place (caused by a call to the slot \ref replot). It is safe to mutually connect the replot slot with this signal on two QCustomPlots to make them replot synchronously, it won't cause an infinite recursion. \see replot, beforeReplot */ /* end of documentation of signals */ /* start of documentation of public members */ /*! \var QCPAxis *QCustomPlot::xAxis A pointer to the primary x Axis (bottom) of the main axis rect of the plot. QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref yAxis2) and the \ref legend. They make it very easy working with plots that only have a single axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the layout system\endlink to add multiple axis rects or multiple axes to one side, use the \ref QCPAxisRect::axis interface to access the new axes. If one of the four default axes or the default legend is removed due to manipulation of the layout system (e.g. by removing the main axis rect), the corresponding pointers become 0. If an axis convenience pointer is currently zero and a new axis rect or a corresponding axis is added in the place of the main axis rect, QCustomPlot resets the convenience pointers to the according new axes. Similarly the \ref legend convenience pointer will be reset if a legend is added after the main legend was removed before. */ /*! \var QCPAxis *QCustomPlot::yAxis A pointer to the primary y Axis (left) of the main axis rect of the plot. QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref yAxis2) and the \ref legend. They make it very easy working with plots that only have a single axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the layout system\endlink to add multiple axis rects or multiple axes to one side, use the \ref QCPAxisRect::axis interface to access the new axes. If one of the four default axes or the default legend is removed due to manipulation of the layout system (e.g. by removing the main axis rect), the corresponding pointers become 0. If an axis convenience pointer is currently zero and a new axis rect or a corresponding axis is added in the place of the main axis rect, QCustomPlot resets the convenience pointers to the according new axes. Similarly the \ref legend convenience pointer will be reset if a legend is added after the main legend was removed before. */ /*! \var QCPAxis *QCustomPlot::xAxis2 A pointer to the secondary x Axis (top) of the main axis rect of the plot. Secondary axes are invisible by default. Use QCPAxis::setVisible to change this (or use \ref QCPAxisRect::setupFullAxesBox). QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref yAxis2) and the \ref legend. They make it very easy working with plots that only have a single axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the layout system\endlink to add multiple axis rects or multiple axes to one side, use the \ref QCPAxisRect::axis interface to access the new axes. If one of the four default axes or the default legend is removed due to manipulation of the layout system (e.g. by removing the main axis rect), the corresponding pointers become 0. If an axis convenience pointer is currently zero and a new axis rect or a corresponding axis is added in the place of the main axis rect, QCustomPlot resets the convenience pointers to the according new axes. Similarly the \ref legend convenience pointer will be reset if a legend is added after the main legend was removed before. */ /*! \var QCPAxis *QCustomPlot::yAxis2 A pointer to the secondary y Axis (right) of the main axis rect of the plot. Secondary axes are invisible by default. Use QCPAxis::setVisible to change this (or use \ref QCPAxisRect::setupFullAxesBox). QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref yAxis2) and the \ref legend. They make it very easy working with plots that only have a single axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the layout system\endlink to add multiple axis rects or multiple axes to one side, use the \ref QCPAxisRect::axis interface to access the new axes. If one of the four default axes or the default legend is removed due to manipulation of the layout system (e.g. by removing the main axis rect), the corresponding pointers become 0. If an axis convenience pointer is currently zero and a new axis rect or a corresponding axis is added in the place of the main axis rect, QCustomPlot resets the convenience pointers to the according new axes. Similarly the \ref legend convenience pointer will be reset if a legend is added after the main legend was removed before. */ /*! \var QCPLegend *QCustomPlot::legend A pointer to the default legend of the main axis rect. The legend is invisible by default. Use QCPLegend::setVisible to change this. QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref yAxis2) and the \ref legend. They make it very easy working with plots that only have a single axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the layout system\endlink to add multiple legends to the plot, use the layout system interface to access the new legend. For example, legends can be placed inside an axis rect's \ref QCPAxisRect::insetLayout "inset layout", and must then also be accessed via the inset layout. If the default legend is removed due to manipulation of the layout system (e.g. by removing the main axis rect), the corresponding pointer becomes 0. If an axis convenience pointer is currently zero and a new axis rect or a corresponding axis is added in the place of the main axis rect, QCustomPlot resets the convenience pointers to the according new axes. Similarly the \ref legend convenience pointer will be reset if a legend is added after the main legend was removed before. */ /* end of documentation of public members */ /*! Constructs a QCustomPlot and sets reasonable default values. */ QCustomPlot::QCustomPlot(QWidget *parent) - : QWidget(parent), xAxis(0), yAxis(0), xAxis2(0), yAxis2(0), legend(0), + : QWidget(parent), xAxis(nullptr), yAxis(nullptr), xAxis2(nullptr), yAxis2(nullptr), legend(nullptr), mBufferDevicePixelRatio(1.0), // will be adapted to primary screen below - mPlotLayout(0), mAutoAddPlottableToLegend(true), mAntialiasedElements(QCP::aeNone), - mNotAntialiasedElements(QCP::aeNone), mInteractions(0), mSelectionTolerance(8), mNoAntialiasingOnDrag(false), + mPlotLayout(nullptr), mAutoAddPlottableToLegend(true), mAntialiasedElements(QCP::aeNone), + mNotAntialiasedElements(QCP::aeNone), mInteractions(nullptr), mSelectionTolerance(8), mNoAntialiasingOnDrag(false), mBackgroundBrush(Qt::white, Qt::SolidPattern), mBackgroundScaled(true), - mBackgroundScaledMode(Qt::KeepAspectRatioByExpanding), mCurrentLayer(0), + mBackgroundScaledMode(Qt::KeepAspectRatioByExpanding), mCurrentLayer(nullptr), mPlottingHints(QCP::phCacheLabels | QCP::phImmediateRefresh), mMultiSelectModifier(Qt::ControlModifier), - mSelectionRectMode(QCP::srmNone), mSelectionRect(0), mOpenGl(false), mMouseHasMoved(false), - mMouseEventLayerable(0), mReplotting(false), mReplotQueued(false), mOpenGlMultisamples(16), + mSelectionRectMode(QCP::srmNone), mSelectionRect(nullptr), mOpenGl(false), mMouseHasMoved(false), + mMouseEventLayerable(nullptr), mReplotting(false), mReplotQueued(false), mOpenGlMultisamples(16), mOpenGlAntialiasedElementsBackup(QCP::aeNone), mOpenGlCacheLabelsBackup(true) { setAttribute(Qt::WA_NoMousePropagation); setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_AcceptTouchEvents); grabGesture(Qt::PinchGesture); setFocusPolicy(Qt::ClickFocus); setMouseTracking(true); QLocale currentLocale = locale(); currentLocale.setNumberOptions(QLocale::OmitGroupSeparator); setLocale(currentLocale); #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED setBufferDevicePixelRatio(QWidget::devicePixelRatio()); #endif mOpenGlAntialiasedElementsBackup = mAntialiasedElements; mOpenGlCacheLabelsBackup = mPlottingHints.testFlag(QCP::phCacheLabels); // create initial layers: mLayers.append(new QCPLayer(this, QLatin1String("background"))); mLayers.append(new QCPLayer(this, QLatin1String("grid"))); mLayers.append(new QCPLayer(this, QLatin1String("main"))); mLayers.append(new QCPLayer(this, QLatin1String("axes"))); mLayers.append(new QCPLayer(this, QLatin1String("legend"))); mLayers.append(new QCPLayer(this, QLatin1String("overlay"))); updateLayerIndices(); setCurrentLayer(QLatin1String("main")); layer(QLatin1String("overlay"))->setMode(QCPLayer::lmBuffered); // create initial layout, axis rect and legend: mPlotLayout = new QCPLayoutGrid; mPlotLayout->initializeParentPlot(this); mPlotLayout->setParent( this); // important because if parent is QWidget, QCPLayout::sizeConstraintsChanged will call QWidget::updateGeometry mPlotLayout->setLayer(QLatin1String("main")); QCPAxisRect *defaultAxisRect = new QCPAxisRect(this, true); mPlotLayout->addElement(0, 0, defaultAxisRect); xAxis = defaultAxisRect->axis(QCPAxis::atBottom); yAxis = defaultAxisRect->axis(QCPAxis::atLeft); xAxis2 = defaultAxisRect->axis(QCPAxis::atTop); yAxis2 = defaultAxisRect->axis(QCPAxis::atRight); legend = new QCPLegend; legend->setVisible(false); defaultAxisRect->insetLayout()->addElement(legend, Qt::AlignRight | Qt::AlignTop); defaultAxisRect->insetLayout()->setMargins(QMargins(12, 12, 12, 12)); defaultAxisRect->setLayer(QLatin1String("background")); xAxis->setLayer(QLatin1String("axes")); yAxis->setLayer(QLatin1String("axes")); xAxis2->setLayer(QLatin1String("axes")); yAxis2->setLayer(QLatin1String("axes")); xAxis->grid()->setLayer(QLatin1String("grid")); yAxis->grid()->setLayer(QLatin1String("grid")); xAxis2->grid()->setLayer(QLatin1String("grid")); yAxis2->grid()->setLayer(QLatin1String("grid")); legend->setLayer(QLatin1String("legend")); // create selection rect instance: mSelectionRect = new QCPSelectionRect(this); mSelectionRect->setLayer(QLatin1String("overlay")); setViewport(rect()); // needs to be called after mPlotLayout has been created replot(rpQueuedReplot); } QCustomPlot::~QCustomPlot() { clearPlottables(); clearItems(); if (mPlotLayout) { delete mPlotLayout; - mPlotLayout = 0; + mPlotLayout = nullptr; } - mCurrentLayer = 0; + mCurrentLayer = nullptr; qDeleteAll(mLayers); // don't use removeLayer, because it would prevent the last layer to be removed mLayers.clear(); } /*! Sets which elements are forcibly drawn antialiased as an \a or combination of QCP::AntialiasedElement. This overrides the antialiasing settings for whole element groups, normally controlled with the \a setAntialiasing function on the individual elements. If an element is neither specified in \ref setAntialiasedElements nor in \ref setNotAntialiasedElements, the antialiasing setting on each individual element instance is used. For example, if \a antialiasedElements contains \ref QCP::aePlottables, all plottables will be drawn antialiased, no matter what the specific QCPAbstractPlottable::setAntialiased value was set to. if an element in \a antialiasedElements is already set in \ref setNotAntialiasedElements, it is removed from there. \see setNotAntialiasedElements */ void QCustomPlot::setAntialiasedElements(const QCP::AntialiasedElements &antialiasedElements) { mAntialiasedElements = antialiasedElements; // make sure elements aren't in mNotAntialiasedElements and mAntialiasedElements simultaneously: if ((mNotAntialiasedElements & mAntialiasedElements) != 0) mNotAntialiasedElements |= ~mAntialiasedElements; } /*! Sets whether the specified \a antialiasedElement is forcibly drawn antialiased. See \ref setAntialiasedElements for details. \see setNotAntialiasedElement */ void QCustomPlot::setAntialiasedElement(QCP::AntialiasedElement antialiasedElement, bool enabled) { if (!enabled && mAntialiasedElements.testFlag(antialiasedElement)) mAntialiasedElements &= ~antialiasedElement; else if (enabled && !mAntialiasedElements.testFlag(antialiasedElement)) mAntialiasedElements |= antialiasedElement; // make sure elements aren't in mNotAntialiasedElements and mAntialiasedElements simultaneously: if ((mNotAntialiasedElements & mAntialiasedElements) != 0) mNotAntialiasedElements |= ~mAntialiasedElements; } /*! Sets which elements are forcibly drawn not antialiased as an \a or combination of QCP::AntialiasedElement. This overrides the antialiasing settings for whole element groups, normally controlled with the \a setAntialiasing function on the individual elements. If an element is neither specified in \ref setAntialiasedElements nor in \ref setNotAntialiasedElements, the antialiasing setting on each individual element instance is used. For example, if \a notAntialiasedElements contains \ref QCP::aePlottables, no plottables will be drawn antialiased, no matter what the specific QCPAbstractPlottable::setAntialiased value was set to. if an element in \a notAntialiasedElements is already set in \ref setAntialiasedElements, it is removed from there. \see setAntialiasedElements */ void QCustomPlot::setNotAntialiasedElements(const QCP::AntialiasedElements ¬AntialiasedElements) { mNotAntialiasedElements = notAntialiasedElements; // make sure elements aren't in mNotAntialiasedElements and mAntialiasedElements simultaneously: if ((mNotAntialiasedElements & mAntialiasedElements) != 0) mAntialiasedElements |= ~mNotAntialiasedElements; } /*! Sets whether the specified \a notAntialiasedElement is forcibly drawn not antialiased. See \ref setNotAntialiasedElements for details. \see setAntialiasedElement */ void QCustomPlot::setNotAntialiasedElement(QCP::AntialiasedElement notAntialiasedElement, bool enabled) { if (!enabled && mNotAntialiasedElements.testFlag(notAntialiasedElement)) mNotAntialiasedElements &= ~notAntialiasedElement; else if (enabled && !mNotAntialiasedElements.testFlag(notAntialiasedElement)) mNotAntialiasedElements |= notAntialiasedElement; // make sure elements aren't in mNotAntialiasedElements and mAntialiasedElements simultaneously: if ((mNotAntialiasedElements & mAntialiasedElements) != 0) mAntialiasedElements |= ~mNotAntialiasedElements; } /*! If set to true, adding a plottable (e.g. a graph) to the QCustomPlot automatically also adds the plottable to the legend (QCustomPlot::legend). \see addGraph, QCPLegend::addItem */ void QCustomPlot::setAutoAddPlottableToLegend(bool on) { mAutoAddPlottableToLegend = on; } /*! Sets the possible interactions of this QCustomPlot as an or-combination of \ref QCP::Interaction enums. There are the following types of interactions: Axis range manipulation is controlled via \ref QCP::iRangeDrag and \ref QCP::iRangeZoom. When the respective interaction is enabled, the user may drag axes ranges and zoom with the mouse wheel. For details how to control which axes the user may drag/zoom and in what orientations, see \ref QCPAxisRect::setRangeDrag, \ref QCPAxisRect::setRangeZoom, \ref QCPAxisRect::setRangeDragAxes, \ref QCPAxisRect::setRangeZoomAxes. Plottable data selection is controlled by \ref QCP::iSelectPlottables. If \ref QCP::iSelectPlottables is set, the user may select plottables (graphs, curves, bars,...) and their data by clicking on them or in their vicinity (\ref setSelectionTolerance). Whether the user can actually select a plottable and its data can further be restricted with the \ref QCPAbstractPlottable::setSelectable method on the specific plottable. For details, see the special page about the \ref dataselection "data selection mechanism". To retrieve a list of all currently selected plottables, call \ref selectedPlottables. If you're only interested in QCPGraphs, you may use the convenience function \ref selectedGraphs. Item selection is controlled by \ref QCP::iSelectItems. If \ref QCP::iSelectItems is set, the user may select items (QCPItemLine, QCPItemText,...) by clicking on them or in their vicinity. To find out whether a specific item is selected, call QCPAbstractItem::selected(). To retrieve a list of all currently selected items, call \ref selectedItems. Axis selection is controlled with \ref QCP::iSelectAxes. If \ref QCP::iSelectAxes is set, the user may select parts of the axes by clicking on them. What parts exactly (e.g. Axis base line, tick labels, axis label) are selectable can be controlled via \ref QCPAxis::setSelectableParts for each axis. To retrieve a list of all axes that currently contain selected parts, call \ref selectedAxes. Which parts of an axis are selected, can be retrieved with QCPAxis::selectedParts(). Legend selection is controlled with \ref QCP::iSelectLegend. If this is set, the user may select the legend itself or individual items by clicking on them. What parts exactly are selectable can be controlled via \ref QCPLegend::setSelectableParts. To find out whether the legend or any of its child items are selected, check the value of QCPLegend::selectedParts. To find out which child items are selected, call \ref QCPLegend::selectedItems. All other selectable elements The selection of all other selectable objects (e.g. QCPTextElement, or your own layerable subclasses) is controlled with \ref QCP::iSelectOther. If set, the user may select those objects by clicking on them. To find out which are currently selected, you need to check their selected state explicitly. If the selection state has changed by user interaction, the \ref selectionChangedByUser signal is emitted. Each selectable object additionally emits an individual selectionChanged signal whenever their selection state has changed, i.e. not only by user interaction. To allow multiple objects to be selected by holding the selection modifier (\ref setMultiSelectModifier), set the flag \ref QCP::iMultiSelect. \note In addition to the selection mechanism presented here, QCustomPlot always emits corresponding signals, when an object is clicked or double clicked. see \ref plottableClick and \ref plottableDoubleClick for example. \see setInteraction, setSelectionTolerance */ void QCustomPlot::setInteractions(const QCP::Interactions &interactions) { mInteractions = interactions; } /*! Sets the single \a interaction of this QCustomPlot to \a enabled. For details about the interaction system, see \ref setInteractions. \see setInteractions */ void QCustomPlot::setInteraction(const QCP::Interaction &interaction, bool enabled) { if (!enabled && mInteractions.testFlag(interaction)) mInteractions &= ~interaction; else if (enabled && !mInteractions.testFlag(interaction)) mInteractions |= interaction; } /*! Sets the tolerance that is used to decide whether a click selects an object (e.g. a plottable) or not. If the user clicks in the vicinity of the line of e.g. a QCPGraph, it's only regarded as a potential selection when the minimum distance between the click position and the graph line is smaller than \a pixels. Objects that are defined by an area (e.g. QCPBars) only react to clicks directly inside the area and ignore this selection tolerance. In other words, it only has meaning for parts of objects that are too thin to exactly hit with a click and thus need such a tolerance. \see setInteractions, QCPLayerable::selectTest */ void QCustomPlot::setSelectionTolerance(int pixels) { mSelectionTolerance = pixels; } /*! Sets whether antialiasing is disabled for this QCustomPlot while the user is dragging axes ranges. If many objects, especially plottables, are drawn antialiased, this greatly improves performance during dragging. Thus it creates a more responsive user experience. As soon as the user stops dragging, the last replot is done with normal antialiasing, to restore high image quality. \see setAntialiasedElements, setNotAntialiasedElements */ void QCustomPlot::setNoAntialiasingOnDrag(bool enabled) { mNoAntialiasingOnDrag = enabled; } /*! Sets the plotting hints for this QCustomPlot instance as an \a or combination of QCP::PlottingHint. \see setPlottingHint */ void QCustomPlot::setPlottingHints(const QCP::PlottingHints &hints) { mPlottingHints = hints; } /*! Sets the specified plotting \a hint to \a enabled. \see setPlottingHints */ void QCustomPlot::setPlottingHint(QCP::PlottingHint hint, bool enabled) { QCP::PlottingHints newHints = mPlottingHints; if (!enabled) newHints &= ~hint; else newHints |= hint; if (newHints != mPlottingHints) setPlottingHints(newHints); } /*! Sets the keyboard modifier that will be recognized as multi-select-modifier. If \ref QCP::iMultiSelect is specified in \ref setInteractions, the user may select multiple objects (or data points) by clicking on them one after the other while holding down \a modifier. By default the multi-select-modifier is set to Qt::ControlModifier. \see setInteractions */ void QCustomPlot::setMultiSelectModifier(Qt::KeyboardModifier modifier) { mMultiSelectModifier = modifier; } /*! Sets how QCustomPlot processes mouse click-and-drag interactions by the user. If \a mode is \ref QCP::srmNone, the mouse drag is forwarded to the underlying objects. For example, QCPAxisRect may process a mouse drag by dragging axis ranges, see \ref QCPAxisRect::setRangeDrag. If \a mode is not \ref QCP::srmNone, the current selection rect (\ref selectionRect) becomes activated and allows e.g. rect zooming and data point selection. If you wish to provide your user both with axis range dragging and data selection/range zooming, use this method to switch between the modes just before the interaction is processed, e.g. in reaction to the \ref mousePress or \ref mouseMove signals. For example you could check whether the user is holding a certain keyboard modifier, and then decide which \a mode shall be set. If a selection rect interaction is currently active, and \a mode is set to \ref QCP::srmNone, the interaction is canceled (\ref QCPSelectionRect::cancel). Switching between any of the other modes will keep the selection rect active. Upon completion of the interaction, the behaviour is as defined by the currently set \a mode, not the mode that was set when the interaction started. \see setInteractions, setSelectionRect, QCPSelectionRect */ void QCustomPlot::setSelectionRectMode(QCP::SelectionRectMode mode) { if (mSelectionRect) { if (mode == QCP::srmNone) mSelectionRect ->cancel(); // when switching to none, we immediately want to abort a potentially active selection rect // disconnect old connections: if (mSelectionRectMode == QCP::srmSelect) disconnect(mSelectionRect, SIGNAL(accepted(QRect, QMouseEvent *)), this, SLOT(processRectSelection(QRect, QMouseEvent *))); else if (mSelectionRectMode == QCP::srmZoom) disconnect(mSelectionRect, SIGNAL(accepted(QRect, QMouseEvent *)), this, SLOT(processRectZoom(QRect, QMouseEvent *))); // establish new ones: if (mode == QCP::srmSelect) connect(mSelectionRect, SIGNAL(accepted(QRect, QMouseEvent *)), this, SLOT(processRectSelection(QRect, QMouseEvent *))); else if (mode == QCP::srmZoom) connect(mSelectionRect, SIGNAL(accepted(QRect, QMouseEvent *)), this, SLOT(processRectZoom(QRect, QMouseEvent *))); } mSelectionRectMode = mode; } /*! Sets the \ref QCPSelectionRect instance that QCustomPlot will use if \a mode is not \ref QCP::srmNone and the user performs a click-and-drag interaction. QCustomPlot takes ownership of the passed \a selectionRect. It can be accessed later via \ref selectionRect. This method is useful if you wish to replace the default QCPSelectionRect instance with an instance of a QCPSelectionRect subclass, to introduce custom behaviour of the selection rect. \see setSelectionRectMode */ void QCustomPlot::setSelectionRect(QCPSelectionRect *selectionRect) { if (mSelectionRect) delete mSelectionRect; mSelectionRect = selectionRect; if (mSelectionRect) { // establish connections with new selection rect: if (mSelectionRectMode == QCP::srmSelect) connect(mSelectionRect, SIGNAL(accepted(QRect, QMouseEvent *)), this, SLOT(processRectSelection(QRect, QMouseEvent *))); else if (mSelectionRectMode == QCP::srmZoom) connect(mSelectionRect, SIGNAL(accepted(QRect, QMouseEvent *)), this, SLOT(processRectZoom(QRect, QMouseEvent *))); } } /*! This method allows to enable OpenGL plot rendering, for increased plotting performance of graphically demanding plots (thick lines, translucent fills, etc.). If \a enabled is set to true, QCustomPlot will try to initialize OpenGL and, if successful, continue plotting with hardware acceleration. The parameter \a multisampling controls how many samples will be used per pixel, it essentially controls the antialiasing quality. If \a multisampling is set too high for the current graphics hardware, the maximum allowed value will be used. You can test whether switching to OpenGL rendering was successful by checking whether the according getter \a QCustomPlot::openGl() returns true. If the OpenGL initialization fails, rendering continues with the regular software rasterizer, and an according qDebug output is generated. If switching to OpenGL was successful, this method disables label caching (\ref setPlottingHint "setPlottingHint(QCP::phCacheLabels, false)") and turns on QCustomPlot's antialiasing override for all elements (\ref setAntialiasedElements "setAntialiasedElements(QCP::aeAll)"), leading to a higher quality output. The antialiasing override allows for pixel-grid aligned drawing in the OpenGL paint device. As stated before, in OpenGL rendering the actual antialiasing of the plot is controlled with \a multisampling. If \a enabled is set to false, the antialiasing/label caching settings are restored to what they were before OpenGL was enabled, if they weren't altered in the meantime. \note OpenGL support is only enabled if QCustomPlot is compiled with the macro \c QCUSTOMPLOT_USE_OPENGL defined. This define must be set before including the QCustomPlot header both during compilation of the QCustomPlot library as well as when compiling your application. It is best to just include the line DEFINES += QCUSTOMPLOT_USE_OPENGL in the respective qmake project files. \note If you are using a Qt version before 5.0, you must also add the module "opengl" to your \c QT variable in the qmake project files. For Qt versions 5.0 and higher, QCustomPlot switches to a newer OpenGL interface which is already in the "gui" module. */ void QCustomPlot::setOpenGl(bool enabled, int multisampling) { mOpenGlMultisamples = qMax(0, multisampling); #ifdef QCUSTOMPLOT_USE_OPENGL mOpenGl = enabled; if (mOpenGl) { if (setupOpenGl()) { // backup antialiasing override and labelcaching setting so we can restore upon disabling OpenGL mOpenGlAntialiasedElementsBackup = mAntialiasedElements; mOpenGlCacheLabelsBackup = mPlottingHints.testFlag(QCP::phCacheLabels); // set antialiasing override to antialias all (aligns gl pixel grid properly), and disable label caching (would use software rasterizer for pixmap caches): setAntialiasedElements(QCP::aeAll); setPlottingHint(QCP::phCacheLabels, false); } else { qDebug() << Q_FUNC_INFO << "Failed to enable OpenGL, continuing plotting without hardware acceleration."; mOpenGl = false; } } else { // restore antialiasing override and labelcaching to what it was before enabling OpenGL, if nobody changed it in the meantime: if (mAntialiasedElements == QCP::aeAll) setAntialiasedElements(mOpenGlAntialiasedElementsBackup); if (!mPlottingHints.testFlag(QCP::phCacheLabels)) setPlottingHint(QCP::phCacheLabels, mOpenGlCacheLabelsBackup); freeOpenGl(); } // recreate all paint buffers: mPaintBuffers.clear(); setupPaintBuffers(); #else Q_UNUSED(enabled) qDebug() << Q_FUNC_INFO << "QCustomPlot can't use OpenGL because QCUSTOMPLOT_USE_OPENGL was not defined during compilation (add " "'DEFINES += QCUSTOMPLOT_USE_OPENGL' to your qmake .pro file)"; #endif } /*! Sets the viewport of this QCustomPlot. Usually users of QCustomPlot don't need to change the viewport manually. The viewport is the area in which the plot is drawn. All mechanisms, e.g. margin caluclation take the viewport to be the outer border of the plot. The viewport normally is the rect() of the QCustomPlot widget, i.e. a rect with top left (0, 0) and size of the QCustomPlot widget. Don't confuse the viewport with the axis rect (QCustomPlot::axisRect). An axis rect is typically an area enclosed by four axes, where the graphs/plottables are drawn in. The viewport is larger and contains also the axes themselves, their tick numbers, their labels, or even additional axis rects, color scales and other layout elements. This function is used to allow arbitrary size exports with \ref toPixmap, \ref savePng, \ref savePdf, etc. by temporarily changing the viewport size. */ void QCustomPlot::setViewport(const QRect &rect) { mViewport = rect; if (mPlotLayout) mPlotLayout->setOuterRect(mViewport); } /*! Sets the device pixel ratio used by the paint buffers of this QCustomPlot instance. Normally, this doesn't need to be set manually, because it is initialized with the regular \a QWidget::devicePixelRatio which is configured by Qt to fit the display device (e.g. 1 for normal displays, 2 for High-DPI displays). Device pixel ratios are supported by Qt only for Qt versions since 5.4. If this method is called when QCustomPlot is being used with older Qt versions, outputs an according qDebug message and leaves the internal buffer device pixel ratio at 1.0. */ void QCustomPlot::setBufferDevicePixelRatio(double ratio) { if (!qFuzzyCompare(ratio, mBufferDevicePixelRatio)) { #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED mBufferDevicePixelRatio = ratio; for (int i = 0; i < mPaintBuffers.size(); ++i) mPaintBuffers.at(i)->setDevicePixelRatio(mBufferDevicePixelRatio); // Note: axis label cache has devicePixelRatio as part of cache hash, so no need to manually clear cache here #else qDebug() << Q_FUNC_INFO << "Device pixel ratios not supported for Qt versions before 5.4"; mBufferDevicePixelRatio = 1.0; #endif } } /*! Sets \a pm as the viewport background pixmap (see \ref setViewport). The pixmap is always drawn below all other objects in the plot. For cases where the provided pixmap doesn't have the same size as the viewport, scaling can be enabled with \ref setBackgroundScaled and the scaling mode (whether and how the aspect ratio is preserved) can be set with \ref setBackgroundScaledMode. To set all these options in one call, consider using the overloaded version of this function. If a background brush was set with \ref setBackground(const QBrush &brush), the viewport will first be filled with that brush, before drawing the background pixmap. This can be useful for background pixmaps with translucent areas. \see setBackgroundScaled, setBackgroundScaledMode */ void QCustomPlot::setBackground(const QPixmap &pm) { mBackgroundPixmap = pm; mScaledBackgroundPixmap = QPixmap(); } /*! Sets the background brush of the viewport (see \ref setViewport). Before drawing everything else, the background is filled with \a brush. If a background pixmap was set with \ref setBackground(const QPixmap &pm), this brush will be used to fill the viewport before the background pixmap is drawn. This can be useful for background pixmaps with translucent areas. Set \a brush to Qt::NoBrush or Qt::Transparent to leave background transparent. This can be useful for exporting to image formats which support transparency, e.g. \ref savePng. \see setBackgroundScaled, setBackgroundScaledMode */ void QCustomPlot::setBackground(const QBrush &brush) { mBackgroundBrush = brush; } /*! \overload Allows setting the background pixmap of the viewport, whether it shall be scaled and how it shall be scaled in one call. \see setBackground(const QPixmap &pm), setBackgroundScaled, setBackgroundScaledMode */ void QCustomPlot::setBackground(const QPixmap &pm, bool scaled, Qt::AspectRatioMode mode) { mBackgroundPixmap = pm; mScaledBackgroundPixmap = QPixmap(); mBackgroundScaled = scaled; mBackgroundScaledMode = mode; } /*! Sets whether the viewport background pixmap shall be scaled to fit the viewport. If \a scaled is set to true, control whether and how the aspect ratio of the original pixmap is preserved with \ref setBackgroundScaledMode. Note that the scaled version of the original pixmap is buffered, so there is no performance penalty on replots. (Except when the viewport dimensions are changed continuously.) \see setBackground, setBackgroundScaledMode */ void QCustomPlot::setBackgroundScaled(bool scaled) { mBackgroundScaled = scaled; } /*! If scaling of the viewport background pixmap is enabled (\ref setBackgroundScaled), use this function to define whether and how the aspect ratio of the original pixmap is preserved. \see setBackground, setBackgroundScaled */ void QCustomPlot::setBackgroundScaledMode(Qt::AspectRatioMode mode) { mBackgroundScaledMode = mode; } /*! Returns the plottable with \a index. If the index is invalid, returns 0. There is an overloaded version of this function with no parameter which returns the last added plottable, see QCustomPlot::plottable() \see plottableCount */ QCPAbstractPlottable *QCustomPlot::plottable(int index) { if (index >= 0 && index < mPlottables.size()) { return mPlottables.at(index); } else { qDebug() << Q_FUNC_INFO << "index out of bounds:" << index; - return 0; + return nullptr; } } /*! \overload Returns the last plottable that was added to the plot. If there are no plottables in the plot, returns 0. \see plottableCount */ QCPAbstractPlottable *QCustomPlot::plottable() { if (!mPlottables.isEmpty()) { return mPlottables.last(); } else - return 0; + return nullptr; } /*! Removes the specified plottable from the plot and deletes it. If necessary, the corresponding legend item is also removed from the default legend (QCustomPlot::legend). Returns true on success. \see clearPlottables */ bool QCustomPlot::removePlottable(QCPAbstractPlottable *plottable) { if (!mPlottables.contains(plottable)) { qDebug() << Q_FUNC_INFO << "plottable not in list:" << reinterpret_cast(plottable); return false; } // remove plottable from legend: plottable->removeFromLegend(); // special handling for QCPGraphs to maintain the simple graph interface: if (QCPGraph *graph = qobject_cast(plottable)) mGraphs.removeOne(graph); // remove plottable: delete plottable; mPlottables.removeOne(plottable); return true; } /*! \overload Removes and deletes the plottable by its \a index. */ bool QCustomPlot::removePlottable(int index) { if (index >= 0 && index < mPlottables.size()) return removePlottable(mPlottables[index]); else { qDebug() << Q_FUNC_INFO << "index out of bounds:" << index; return false; } } /*! Removes all plottables from the plot and deletes them. Corresponding legend items are also removed from the default legend (QCustomPlot::legend). Returns the number of plottables removed. \see removePlottable */ int QCustomPlot::clearPlottables() { int c = mPlottables.size(); for (int i = c - 1; i >= 0; --i) removePlottable(mPlottables[i]); return c; } /*! Returns the number of currently existing plottables in the plot \see plottable */ int QCustomPlot::plottableCount() const { return mPlottables.size(); } /*! Returns a list of the selected plottables. If no plottables are currently selected, the list is empty. There is a convenience function if you're only interested in selected graphs, see \ref selectedGraphs. \see setInteractions, QCPAbstractPlottable::setSelectable, QCPAbstractPlottable::setSelection */ QList QCustomPlot::selectedPlottables() const { QList result; foreach (QCPAbstractPlottable *plottable, mPlottables) { if (plottable->selected()) result.append(plottable); } return result; } /*! Returns the plottable at the pixel position \a pos. Plottables that only consist of single lines (like graphs) have a tolerance band around them, see \ref setSelectionTolerance. If multiple plottables come into consideration, the one closest to \a pos is returned. If \a onlySelectable is true, only plottables that are selectable (QCPAbstractPlottable::setSelectable) are considered. If there is no plottable at \a pos, the return value is 0. \see itemAt, layoutElementAt */ QCPAbstractPlottable *QCustomPlot::plottableAt(const QPointF &pos, bool onlySelectable) const { - QCPAbstractPlottable *resultPlottable = 0; + QCPAbstractPlottable *resultPlottable = nullptr; double resultDistance = mSelectionTolerance; // only regard clicks with distances smaller than mSelectionTolerance as selections, so initialize with that value foreach (QCPAbstractPlottable *plottable, mPlottables) { if (onlySelectable && !plottable ->selectable()) // we could have also passed onlySelectable to the selectTest function, but checking here is faster, because we have access to QCPabstractPlottable::selectable continue; if ((plottable->keyAxis()->axisRect()->rect() & plottable->valueAxis()->axisRect()->rect()) .contains( pos.toPoint())) // only consider clicks inside the rect that is spanned by the plottable's key/value axes { double currentDistance = plottable->selectTest(pos, false); if (currentDistance >= 0 && currentDistance < resultDistance) { resultPlottable = plottable; resultDistance = currentDistance; } } } return resultPlottable; } /*! Returns whether this QCustomPlot instance contains the \a plottable. */ bool QCustomPlot::hasPlottable(QCPAbstractPlottable *plottable) const { return mPlottables.contains(plottable); } /*! Returns the graph with \a index. If the index is invalid, returns 0. There is an overloaded version of this function with no parameter which returns the last created graph, see QCustomPlot::graph() \see graphCount, addGraph */ QCPGraph *QCustomPlot::graph(int index) const { if (index >= 0 && index < mGraphs.size()) { return mGraphs.at(index); } else { qDebug() << Q_FUNC_INFO << "index out of bounds:" << index; - return 0; + return nullptr; } } /*! \overload Returns the last graph, that was created with \ref addGraph. If there are no graphs in the plot, returns 0. \see graphCount, addGraph */ QCPGraph *QCustomPlot::graph() const { if (!mGraphs.isEmpty()) { return mGraphs.last(); } else - return 0; + return nullptr; } /*! Creates a new graph inside the plot. If \a keyAxis and \a valueAxis are left unspecified (0), the bottom (xAxis) is used as key and the left (yAxis) is used as value axis. If specified, \a keyAxis and \a valueAxis must reside in this QCustomPlot. \a keyAxis will be used as key axis (typically "x") and \a valueAxis as value axis (typically "y") for the graph. Returns a pointer to the newly created graph, or 0 if adding the graph failed. \see graph, graphCount, removeGraph, clearGraphs */ QCPGraph *QCustomPlot::addGraph(QCPAxis *keyAxis, QCPAxis *valueAxis) { if (!keyAxis) keyAxis = xAxis; if (!valueAxis) valueAxis = yAxis; if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "can't use default QCustomPlot xAxis or yAxis, because at least one is invalid (has been deleted)"; - return 0; + return nullptr; } if (keyAxis->parentPlot() != this || valueAxis->parentPlot() != this) { qDebug() << Q_FUNC_INFO << "passed keyAxis or valueAxis doesn't have this QCustomPlot as parent"; - return 0; + return nullptr; } QCPGraph *newGraph = new QCPGraph(keyAxis, valueAxis); newGraph->setName(QLatin1String("Graph ") + QString::number(mGraphs.size())); return newGraph; } /*! Removes the specified \a graph from the plot and deletes it. If necessary, the corresponding legend item is also removed from the default legend (QCustomPlot::legend). If any other graphs in the plot have a channel fill set towards the removed graph, the channel fill property of those graphs is reset to zero (no channel fill). Returns true on success. \see clearGraphs */ bool QCustomPlot::removeGraph(QCPGraph *graph) { return removePlottable(graph); } /*! \overload Removes and deletes the graph by its \a index. */ bool QCustomPlot::removeGraph(int index) { if (index >= 0 && index < mGraphs.size()) return removeGraph(mGraphs[index]); else return false; } /*! Removes all graphs from the plot and deletes them. Corresponding legend items are also removed from the default legend (QCustomPlot::legend). Returns the number of graphs removed. \see removeGraph */ int QCustomPlot::clearGraphs() { int c = mGraphs.size(); for (int i = c - 1; i >= 0; --i) removeGraph(mGraphs[i]); return c; } /*! Returns the number of currently existing graphs in the plot \see graph, addGraph */ int QCustomPlot::graphCount() const { return mGraphs.size(); } /*! Returns a list of the selected graphs. If no graphs are currently selected, the list is empty. If you are not only interested in selected graphs but other plottables like QCPCurve, QCPBars, etc., use \ref selectedPlottables. \see setInteractions, selectedPlottables, QCPAbstractPlottable::setSelectable, QCPAbstractPlottable::setSelection */ QList QCustomPlot::selectedGraphs() const { QList result; foreach (QCPGraph *graph, mGraphs) { if (graph->selected()) result.append(graph); } return result; } /*! Returns the item with \a index. If the index is invalid, returns 0. There is an overloaded version of this function with no parameter which returns the last added item, see QCustomPlot::item() \see itemCount */ QCPAbstractItem *QCustomPlot::item(int index) const { if (index >= 0 && index < mItems.size()) { return mItems.at(index); } else { qDebug() << Q_FUNC_INFO << "index out of bounds:" << index; - return 0; + return nullptr; } } /*! \overload Returns the last item that was added to this plot. If there are no items in the plot, returns 0. \see itemCount */ QCPAbstractItem *QCustomPlot::item() const { if (!mItems.isEmpty()) { return mItems.last(); } else - return 0; + return nullptr; } /*! Removes the specified item from the plot and deletes it. Returns true on success. \see clearItems */ bool QCustomPlot::removeItem(QCPAbstractItem *item) { if (mItems.contains(item)) { delete item; mItems.removeOne(item); return true; } else { qDebug() << Q_FUNC_INFO << "item not in list:" << reinterpret_cast(item); return false; } } /*! \overload Removes and deletes the item by its \a index. */ bool QCustomPlot::removeItem(int index) { if (index >= 0 && index < mItems.size()) return removeItem(mItems[index]); else { qDebug() << Q_FUNC_INFO << "index out of bounds:" << index; return false; } } /*! Removes all items from the plot and deletes them. Returns the number of items removed. \see removeItem */ int QCustomPlot::clearItems() { int c = mItems.size(); for (int i = c - 1; i >= 0; --i) removeItem(mItems[i]); return c; } /*! Returns the number of currently existing items in the plot \see item */ int QCustomPlot::itemCount() const { return mItems.size(); } /*! Returns a list of the selected items. If no items are currently selected, the list is empty. \see setInteractions, QCPAbstractItem::setSelectable, QCPAbstractItem::setSelected */ QList QCustomPlot::selectedItems() const { QList result; foreach (QCPAbstractItem *item, mItems) { if (item->selected()) result.append(item); } return result; } /*! Returns the item at the pixel position \a pos. Items that only consist of single lines (e.g. \ref QCPItemLine or \ref QCPItemCurve) have a tolerance band around them, see \ref setSelectionTolerance. If multiple items come into consideration, the one closest to \a pos is returned. If \a onlySelectable is true, only items that are selectable (QCPAbstractItem::setSelectable) are considered. If there is no item at \a pos, the return value is 0. \see plottableAt, layoutElementAt */ QCPAbstractItem *QCustomPlot::itemAt(const QPointF &pos, bool onlySelectable) const { - QCPAbstractItem *resultItem = 0; + QCPAbstractItem *resultItem = nullptr; double resultDistance = mSelectionTolerance; // only regard clicks with distances smaller than mSelectionTolerance as selections, so initialize with that value foreach (QCPAbstractItem *item, mItems) { if (onlySelectable && !item->selectable()) // we could have also passed onlySelectable to the selectTest function, but checking here is faster, because we have access to QCPAbstractItem::selectable continue; if (!item->clipToAxisRect() || item->clipRect().contains( pos.toPoint())) // only consider clicks inside axis cliprect of the item if actually clipped to it { double currentDistance = item->selectTest(pos, false); if (currentDistance >= 0 && currentDistance < resultDistance) { resultItem = item; resultDistance = currentDistance; } } } return resultItem; } /*! Returns whether this QCustomPlot contains the \a item. \see item */ bool QCustomPlot::hasItem(QCPAbstractItem *item) const { return mItems.contains(item); } /*! Returns the layer with the specified \a name. If there is no layer with the specified name, 0 is returned. Layer names are case-sensitive. \see addLayer, moveLayer, removeLayer */ QCPLayer *QCustomPlot::layer(const QString &name) const { foreach (QCPLayer *layer, mLayers) { if (layer->name() == name) return layer; } - return 0; + return nullptr; } /*! \overload Returns the layer by \a index. If the index is invalid, 0 is returned. \see addLayer, moveLayer, removeLayer */ QCPLayer *QCustomPlot::layer(int index) const { if (index >= 0 && index < mLayers.size()) { return mLayers.at(index); } else { qDebug() << Q_FUNC_INFO << "index out of bounds:" << index; - return 0; + return nullptr; } } /*! Returns the layer that is set as current layer (see \ref setCurrentLayer). */ QCPLayer *QCustomPlot::currentLayer() const { return mCurrentLayer; } /*! Sets the layer with the specified \a name to be the current layer. All layerables (\ref QCPLayerable), e.g. plottables and items, are created on the current layer. Returns true on success, i.e. if there is a layer with the specified \a name in the QCustomPlot. Layer names are case-sensitive. \see addLayer, moveLayer, removeLayer, QCPLayerable::setLayer */ bool QCustomPlot::setCurrentLayer(const QString &name) { if (QCPLayer *newCurrentLayer = layer(name)) { return setCurrentLayer(newCurrentLayer); } else { qDebug() << Q_FUNC_INFO << "layer with name doesn't exist:" << name; return false; } } /*! \overload Sets the provided \a layer to be the current layer. Returns true on success, i.e. when \a layer is a valid layer in the QCustomPlot. \see addLayer, moveLayer, removeLayer */ bool QCustomPlot::setCurrentLayer(QCPLayer *layer) { if (!mLayers.contains(layer)) { qDebug() << Q_FUNC_INFO << "layer not a layer of this QCustomPlot:" << reinterpret_cast(layer); return false; } mCurrentLayer = layer; return true; } /*! Returns the number of currently existing layers in the plot \see layer, addLayer */ int QCustomPlot::layerCount() const { return mLayers.size(); } /*! Adds a new layer to this QCustomPlot instance. The new layer will have the name \a name, which must be unique. Depending on \a insertMode, it is positioned either below or above \a otherLayer. Returns true on success, i.e. if there is no other layer named \a name and \a otherLayer is a valid layer inside this QCustomPlot. If \a otherLayer is 0, the highest layer in the QCustomPlot will be used. For an explanation of what layers are in QCustomPlot, see the documentation of \ref QCPLayer. \see layer, moveLayer, removeLayer */ bool QCustomPlot::addLayer(const QString &name, QCPLayer *otherLayer, QCustomPlot::LayerInsertMode insertMode) { if (!otherLayer) otherLayer = mLayers.last(); if (!mLayers.contains(otherLayer)) { qDebug() << Q_FUNC_INFO << "otherLayer not a layer of this QCustomPlot:" << reinterpret_cast(otherLayer); return false; } if (layer(name)) { qDebug() << Q_FUNC_INFO << "A layer exists already with the name" << name; return false; } QCPLayer *newLayer = new QCPLayer(this, name); mLayers.insert(otherLayer->index() + (insertMode == limAbove ? 1 : 0), newLayer); updateLayerIndices(); setupPaintBuffers(); // associates new layer with the appropriate paint buffer return true; } /*! Removes the specified \a layer and returns true on success. All layerables (e.g. plottables and items) on the removed layer will be moved to the layer below \a layer. If \a layer is the bottom layer, the layerables are moved to the layer above. In both cases, the total rendering order of all layerables in the QCustomPlot is preserved. If \a layer is the current layer (\ref setCurrentLayer), the layer below (or above, if bottom layer) becomes the new current layer. It is not possible to remove the last layer of the plot. \see layer, addLayer, moveLayer */ bool QCustomPlot::removeLayer(QCPLayer *layer) { if (!mLayers.contains(layer)) { qDebug() << Q_FUNC_INFO << "layer not a layer of this QCustomPlot:" << reinterpret_cast(layer); return false; } if (mLayers.size() < 2) { qDebug() << Q_FUNC_INFO << "can't remove last layer"; return false; } // append all children of this layer to layer below (if this is lowest layer, prepend to layer above) int removedIndex = layer->index(); bool isFirstLayer = removedIndex == 0; QCPLayer *targetLayer = isFirstLayer ? mLayers.at(removedIndex + 1) : mLayers.at(removedIndex - 1); QList children = layer->children(); if (isFirstLayer) // prepend in reverse order (so order relative to each other stays the same) { for (int i = children.size() - 1; i >= 0; --i) children.at(i)->moveToLayer(targetLayer, true); } else // append normally { for (int i = 0; i < children.size(); ++i) children.at(i)->moveToLayer(targetLayer, false); } // if removed layer is current layer, change current layer to layer below/above: if (layer == mCurrentLayer) setCurrentLayer(targetLayer); // invalidate the paint buffer that was responsible for this layer: if (!layer->mPaintBuffer.isNull()) layer->mPaintBuffer.data()->setInvalidated(); // remove layer: delete layer; mLayers.removeOne(layer); updateLayerIndices(); return true; } /*! Moves the specified \a layer either above or below \a otherLayer. Whether it's placed above or below is controlled with \a insertMode. Returns true on success, i.e. when both \a layer and \a otherLayer are valid layers in the QCustomPlot. \see layer, addLayer, moveLayer */ bool QCustomPlot::moveLayer(QCPLayer *layer, QCPLayer *otherLayer, QCustomPlot::LayerInsertMode insertMode) { if (!mLayers.contains(layer)) { qDebug() << Q_FUNC_INFO << "layer not a layer of this QCustomPlot:" << reinterpret_cast(layer); return false; } if (!mLayers.contains(otherLayer)) { qDebug() << Q_FUNC_INFO << "otherLayer not a layer of this QCustomPlot:" << reinterpret_cast(otherLayer); return false; } if (layer->index() > otherLayer->index()) mLayers.move(layer->index(), otherLayer->index() + (insertMode == limAbove ? 1 : 0)); else if (layer->index() < otherLayer->index()) mLayers.move(layer->index(), otherLayer->index() + (insertMode == limAbove ? 0 : -1)); // invalidate the paint buffers that are responsible for the layers: if (!layer->mPaintBuffer.isNull()) layer->mPaintBuffer.data()->setInvalidated(); if (!otherLayer->mPaintBuffer.isNull()) otherLayer->mPaintBuffer.data()->setInvalidated(); updateLayerIndices(); return true; } /*! Returns the number of axis rects in the plot. All axis rects can be accessed via QCustomPlot::axisRect(). Initially, only one axis rect exists in the plot. \see axisRect, axisRects */ int QCustomPlot::axisRectCount() const { return axisRects().size(); } /*! Returns the axis rect with \a index. Initially, only one axis rect (with index 0) exists in the plot. If multiple axis rects were added, all of them may be accessed with this function in a linear fashion (even when they are nested in a layout hierarchy or inside other axis rects via QCPAxisRect::insetLayout). \see axisRectCount, axisRects */ QCPAxisRect *QCustomPlot::axisRect(int index) const { const QList rectList = axisRects(); if (index >= 0 && index < rectList.size()) { return rectList.at(index); } else { qDebug() << Q_FUNC_INFO << "invalid axis rect index" << index; - return 0; + return nullptr; } } /*! Returns all axis rects in the plot. \see axisRectCount, axisRect */ QList QCustomPlot::axisRects() const { QList result; QStack elementStack; if (mPlotLayout) elementStack.push(mPlotLayout); while (!elementStack.isEmpty()) { foreach (QCPLayoutElement *element, elementStack.pop()->elements(false)) { if (element) { elementStack.push(element); if (QCPAxisRect *ar = qobject_cast(element)) result.append(ar); } } } return result; } /*! Returns the layout element at pixel position \a pos. If there is no element at that position, returns 0. Only visible elements are used. If \ref QCPLayoutElement::setVisible on the element itself or on any of its parent elements is set to false, it will not be considered. \see itemAt, plottableAt */ QCPLayoutElement *QCustomPlot::layoutElementAt(const QPointF &pos) const { QCPLayoutElement *currentElement = mPlotLayout; bool searchSubElements = true; while (searchSubElements && currentElement) { searchSubElements = false; foreach (QCPLayoutElement *subElement, currentElement->elements(false)) { if (subElement && subElement->realVisibility() && subElement->selectTest(pos, false) >= 0) { currentElement = subElement; searchSubElements = true; break; } } } return currentElement; } /*! Returns the layout element of type \ref QCPAxisRect at pixel position \a pos. This method ignores other layout elements even if they are visually in front of the axis rect (e.g. a \ref QCPLegend). If there is no axis rect at that position, returns 0. Only visible axis rects are used. If \ref QCPLayoutElement::setVisible on the axis rect itself or on any of its parent elements is set to false, it will not be considered. \see layoutElementAt */ QCPAxisRect *QCustomPlot::axisRectAt(const QPointF &pos) const { - QCPAxisRect *result = 0; + QCPAxisRect *result = nullptr; QCPLayoutElement *currentElement = mPlotLayout; bool searchSubElements = true; while (searchSubElements && currentElement) { searchSubElements = false; foreach (QCPLayoutElement *subElement, currentElement->elements(false)) { if (subElement && subElement->realVisibility() && subElement->selectTest(pos, false) >= 0) { currentElement = subElement; searchSubElements = true; if (QCPAxisRect *ar = qobject_cast(currentElement)) result = ar; break; } } } return result; } /*! Returns the axes that currently have selected parts, i.e. whose selection state is not \ref QCPAxis::spNone. \see selectedPlottables, selectedLegends, setInteractions, QCPAxis::setSelectedParts, QCPAxis::setSelectableParts */ QList QCustomPlot::selectedAxes() const { QList result, allAxes; foreach (QCPAxisRect *rect, axisRects()) allAxes << rect->axes(); foreach (QCPAxis *axis, allAxes) { if (axis->selectedParts() != QCPAxis::spNone) result.append(axis); } return result; } /*! Returns the legends that currently have selected parts, i.e. whose selection state is not \ref QCPLegend::spNone. \see selectedPlottables, selectedAxes, setInteractions, QCPLegend::setSelectedParts, QCPLegend::setSelectableParts, QCPLegend::selectedItems */ QList QCustomPlot::selectedLegends() const { QList result; QStack elementStack; if (mPlotLayout) elementStack.push(mPlotLayout); while (!elementStack.isEmpty()) { foreach (QCPLayoutElement *subElement, elementStack.pop()->elements(false)) { if (subElement) { elementStack.push(subElement); if (QCPLegend *leg = qobject_cast(subElement)) { if (leg->selectedParts() != QCPLegend::spNone) result.append(leg); } } } } return result; } /*! Deselects all layerables (plottables, items, axes, legends,...) of the QCustomPlot. Since calling this function is not a user interaction, this does not emit the \ref selectionChangedByUser signal. The individual selectionChanged signals are emitted though, if the objects were previously selected. \see setInteractions, selectedPlottables, selectedItems, selectedAxes, selectedLegends */ void QCustomPlot::deselectAll() { foreach (QCPLayer *layer, mLayers) { foreach (QCPLayerable *layerable, layer->children()) - layerable->deselectEvent(0); + layerable->deselectEvent(nullptr); } } /*! Causes a complete replot into the internal paint buffer(s). Finally, the widget surface is refreshed with the new buffer contents. This is the method that must be called to make changes to the plot, e.g. on the axis ranges or data points of graphs, visible. The parameter \a refreshPriority can be used to fine-tune the timing of the replot. For example if your application calls \ref replot very quickly in succession (e.g. multiple independent functions change some aspects of the plot and each wants to make sure the change gets replotted), it is advisable to set \a refreshPriority to \ref QCustomPlot::rpQueuedReplot. This way, the actual replotting is deferred to the next event loop iteration. Multiple successive calls of \ref replot with this priority will only cause a single replot, avoiding redundant replots and improving performance. Under a few circumstances, QCustomPlot causes a replot by itself. Those are resize events of the QCustomPlot widget and user interactions (object selection and range dragging/zooming). Before the replot happens, the signal \ref beforeReplot is emitted. After the replot, \ref afterReplot is emitted. It is safe to mutually connect the replot slot with any of those two signals on two QCustomPlots to make them replot synchronously, it won't cause an infinite recursion. If a layer is in mode \ref QCPLayer::lmBuffered (\ref QCPLayer::setMode), it is also possible to replot only that specific layer via \ref QCPLayer::replot. See the documentation there for details. */ void QCustomPlot::replot(QCustomPlot::RefreshPriority refreshPriority) { if (refreshPriority == QCustomPlot::rpQueuedReplot) { if (!mReplotQueued) { mReplotQueued = true; QTimer::singleShot(0, this, SLOT(replot())); } return; } if (mReplotting) // incase signals loop back to replot slot return; mReplotting = true; mReplotQueued = false; emit beforeReplot(); updateLayout(); // draw all layered objects (grid, axes, plottables, items, legend,...) into their buffers: setupPaintBuffers(); foreach (QCPLayer *layer, mLayers) layer->drawToPaintBuffer(); for (int i = 0; i < mPaintBuffers.size(); ++i) mPaintBuffers.at(i)->setInvalidated(false); if ((refreshPriority == rpRefreshHint && mPlottingHints.testFlag(QCP::phImmediateRefresh)) || refreshPriority == rpImmediateRefresh) repaint(); else update(); emit afterReplot(); mReplotting = false; } /*! Rescales the axes such that all plottables (like graphs) in the plot are fully visible. if \a onlyVisiblePlottables is set to true, only the plottables that have their visibility set to true (QCPLayerable::setVisible), will be used to rescale the axes. \see QCPAbstractPlottable::rescaleAxes, QCPAxis::rescale */ void QCustomPlot::rescaleAxes(bool onlyVisiblePlottables) { QList allAxes; foreach (QCPAxisRect *rect, axisRects()) allAxes << rect->axes(); foreach (QCPAxis *axis, allAxes) axis->rescale(onlyVisiblePlottables); } /*! Saves a PDF with the vectorized plot to the file \a fileName. The axis ratio as well as the scale of texts and lines will be derived from the specified \a width and \a height. This means, the output will look like the normal on-screen output of a QCustomPlot widget with the corresponding pixel width and height. If either \a width or \a height is zero, the exported image will have the same dimensions as the QCustomPlot widget currently has. Setting \a exportPen to \ref QCP::epNoCosmetic allows to disable the use of cosmetic pens when drawing to the PDF file. Cosmetic pens are pens with numerical width 0, which are always drawn as a one pixel wide line, no matter what zoom factor is set in the PDF-Viewer. For more information about cosmetic pens, see the QPainter and QPen documentation. The objects of the plot will appear in the current selection state. If you don't want any selected objects to be painted in their selected look, deselect everything with \ref deselectAll before calling this function. Returns true on success. \warning \li If you plan on editing the exported PDF file with a vector graphics editor like Inkscape, it is advised to set \a exportPen to \ref QCP::epNoCosmetic to avoid losing those cosmetic lines (which might be quite many, because cosmetic pens are the default for e.g. axes and tick marks). \li If calling this function inside the constructor of the parent of the QCustomPlot widget (i.e. the MainWindow constructor, if QCustomPlot is inside the MainWindow), always provide explicit non-zero widths and heights. If you leave \a width or \a height as 0 (default), this function uses the current width and height of the QCustomPlot widget. However, in Qt, these aren't defined yet inside the constructor, so you would get an image that has strange widths/heights. \a pdfCreator and \a pdfTitle may be used to set the according metadata fields in the resulting PDF file. \note On Android systems, this method does nothing and issues an according qDebug warning message. This is also the case if for other reasons the define flag \c QT_NO_PRINTER is set. \see savePng, saveBmp, saveJpg, saveRastered */ bool QCustomPlot::savePdf(const QString &fileName, int width, int height, QCP::ExportPen exportPen, const QString &pdfCreator, const QString &pdfTitle) { bool success = false; #ifdef QT_NO_PRINTER Q_UNUSED(fileName) Q_UNUSED(exportPen) Q_UNUSED(width) Q_UNUSED(height) Q_UNUSED(pdfCreator) Q_UNUSED(pdfTitle) qDebug() << Q_FUNC_INFO << "Qt was built without printer support (QT_NO_PRINTER). PDF not created."; #else int newWidth, newHeight; if (width == 0 || height == 0) { newWidth = this->width(); newHeight = this->height(); } else { newWidth = width; newHeight = height; } QPrinter printer(QPrinter::ScreenResolution); printer.setOutputFileName(fileName); printer.setOutputFormat(QPrinter::PdfFormat); printer.setColorMode(QPrinter::Color); printer.printEngine()->setProperty(QPrintEngine::PPK_Creator, pdfCreator); printer.printEngine()->setProperty(QPrintEngine::PPK_DocumentName, pdfTitle); QRect oldViewport = viewport(); setViewport(QRect(0, 0, newWidth, newHeight)); #if QT_VERSION < QT_VERSION_CHECK(5, 3, 0) printer.setFullPage(true); printer.setPaperSize(viewport().size(), QPrinter::DevicePixel); #else QPageLayout pageLayout; pageLayout.setMode(QPageLayout::FullPageMode); pageLayout.setOrientation(QPageLayout::Portrait); pageLayout.setMargins(QMarginsF(0, 0, 0, 0)); pageLayout.setPageSize(QPageSize(viewport().size(), QPageSize::Point, QString(), QPageSize::ExactMatch)); printer.setPageLayout(pageLayout); #endif QCPPainter printpainter; if (printpainter.begin(&printer)) { printpainter.setMode(QCPPainter::pmVectorized); printpainter.setMode(QCPPainter::pmNoCaching); printpainter.setMode(QCPPainter::pmNonCosmetic, exportPen == QCP::epNoCosmetic); printpainter.setWindow(mViewport); if (mBackgroundBrush.style() != Qt::NoBrush && mBackgroundBrush.color() != Qt::white && mBackgroundBrush.color() != Qt::transparent && mBackgroundBrush.color().alpha() > 0) // draw pdf background color if not white/transparent printpainter.fillRect(viewport(), mBackgroundBrush); draw(&printpainter); printpainter.end(); success = true; } setViewport(oldViewport); #endif // QT_NO_PRINTER return success; } /*! Saves a PNG image file to \a fileName on disc. The output plot will have the dimensions \a width and \a height in pixels, multiplied by \a scale. If either \a width or \a height is zero, the current width and height of the QCustomPlot widget is used instead. Line widths and texts etc. are not scaled up when larger widths/heights are used. If you want that effect, use the \a scale parameter. For example, if you set both \a width and \a height to 100 and \a scale to 2, you will end up with an image file of size 200*200 in which all graphical elements are scaled up by factor 2 (line widths, texts, etc.). This scaling is not done by stretching a 100*100 image, the result will have full 200*200 pixel resolution. If you use a high scaling factor, it is recommended to enable antialiasing for all elements by temporarily setting \ref QCustomPlot::setAntialiasedElements to \ref QCP::aeAll as this allows QCustomPlot to place objects with sub-pixel accuracy. image compression can be controlled with the \a quality parameter which must be between 0 and 100 or -1 to use the default setting. The \a resolution will be written to the image file header and has no direct consequence for the quality or the pixel size. However, if opening the image with a tool which respects the metadata, it will be able to scale the image to match either a given size in real units of length (inch, centimeters, etc.), or the target display DPI. You can specify in which units \a resolution is given, by setting \a resolutionUnit. The \a resolution is converted to the format's expected resolution unit internally. Returns true on success. If this function fails, most likely the PNG format isn't supported by the system, see Qt docs about QImageWriter::supportedImageFormats(). The objects of the plot will appear in the current selection state. If you don't want any selected objects to be painted in their selected look, deselect everything with \ref deselectAll before calling this function. If you want the PNG to have a transparent background, call \ref setBackground(const QBrush &brush) with no brush (Qt::NoBrush) or a transparent color (Qt::transparent), before saving. \warning If calling this function inside the constructor of the parent of the QCustomPlot widget (i.e. the MainWindow constructor, if QCustomPlot is inside the MainWindow), always provide explicit non-zero widths and heights. If you leave \a width or \a height as 0 (default), this function uses the current width and height of the QCustomPlot widget. However, in Qt, these aren't defined yet inside the constructor, so you would get an image that has strange widths/heights. \see savePdf, saveBmp, saveJpg, saveRastered */ bool QCustomPlot::savePng(const QString &fileName, int width, int height, double scale, int quality, int resolution, QCP::ResolutionUnit resolutionUnit) { return saveRastered(fileName, width, height, scale, "PNG", quality, resolution, resolutionUnit); } /*! Saves a JPEG image file to \a fileName on disc. The output plot will have the dimensions \a width and \a height in pixels, multiplied by \a scale. If either \a width or \a height is zero, the current width and height of the QCustomPlot widget is used instead. Line widths and texts etc. are not scaled up when larger widths/heights are used. If you want that effect, use the \a scale parameter. For example, if you set both \a width and \a height to 100 and \a scale to 2, you will end up with an image file of size 200*200 in which all graphical elements are scaled up by factor 2 (line widths, texts, etc.). This scaling is not done by stretching a 100*100 image, the result will have full 200*200 pixel resolution. If you use a high scaling factor, it is recommended to enable antialiasing for all elements by temporarily setting \ref QCustomPlot::setAntialiasedElements to \ref QCP::aeAll as this allows QCustomPlot to place objects with sub-pixel accuracy. image compression can be controlled with the \a quality parameter which must be between 0 and 100 or -1 to use the default setting. The \a resolution will be written to the image file header and has no direct consequence for the quality or the pixel size. However, if opening the image with a tool which respects the metadata, it will be able to scale the image to match either a given size in real units of length (inch, centimeters, etc.), or the target display DPI. You can specify in which units \a resolution is given, by setting \a resolutionUnit. The \a resolution is converted to the format's expected resolution unit internally. Returns true on success. If this function fails, most likely the JPEG format isn't supported by the system, see Qt docs about QImageWriter::supportedImageFormats(). The objects of the plot will appear in the current selection state. If you don't want any selected objects to be painted in their selected look, deselect everything with \ref deselectAll before calling this function. \warning If calling this function inside the constructor of the parent of the QCustomPlot widget (i.e. the MainWindow constructor, if QCustomPlot is inside the MainWindow), always provide explicit non-zero widths and heights. If you leave \a width or \a height as 0 (default), this function uses the current width and height of the QCustomPlot widget. However, in Qt, these aren't defined yet inside the constructor, so you would get an image that has strange widths/heights. \see savePdf, savePng, saveBmp, saveRastered */ bool QCustomPlot::saveJpg(const QString &fileName, int width, int height, double scale, int quality, int resolution, QCP::ResolutionUnit resolutionUnit) { return saveRastered(fileName, width, height, scale, "JPG", quality, resolution, resolutionUnit); } /*! Saves a BMP image file to \a fileName on disc. The output plot will have the dimensions \a width and \a height in pixels, multiplied by \a scale. If either \a width or \a height is zero, the current width and height of the QCustomPlot widget is used instead. Line widths and texts etc. are not scaled up when larger widths/heights are used. If you want that effect, use the \a scale parameter. For example, if you set both \a width and \a height to 100 and \a scale to 2, you will end up with an image file of size 200*200 in which all graphical elements are scaled up by factor 2 (line widths, texts, etc.). This scaling is not done by stretching a 100*100 image, the result will have full 200*200 pixel resolution. If you use a high scaling factor, it is recommended to enable antialiasing for all elements by temporarily setting \ref QCustomPlot::setAntialiasedElements to \ref QCP::aeAll as this allows QCustomPlot to place objects with sub-pixel accuracy. The \a resolution will be written to the image file header and has no direct consequence for the quality or the pixel size. However, if opening the image with a tool which respects the metadata, it will be able to scale the image to match either a given size in real units of length (inch, centimeters, etc.), or the target display DPI. You can specify in which units \a resolution is given, by setting \a resolutionUnit. The \a resolution is converted to the format's expected resolution unit internally. Returns true on success. If this function fails, most likely the BMP format isn't supported by the system, see Qt docs about QImageWriter::supportedImageFormats(). The objects of the plot will appear in the current selection state. If you don't want any selected objects to be painted in their selected look, deselect everything with \ref deselectAll before calling this function. \warning If calling this function inside the constructor of the parent of the QCustomPlot widget (i.e. the MainWindow constructor, if QCustomPlot is inside the MainWindow), always provide explicit non-zero widths and heights. If you leave \a width or \a height as 0 (default), this function uses the current width and height of the QCustomPlot widget. However, in Qt, these aren't defined yet inside the constructor, so you would get an image that has strange widths/heights. \see savePdf, savePng, saveJpg, saveRastered */ bool QCustomPlot::saveBmp(const QString &fileName, int width, int height, double scale, int resolution, QCP::ResolutionUnit resolutionUnit) { return saveRastered(fileName, width, height, scale, "BMP", -1, resolution, resolutionUnit); } /*! \internal Returns a minimum size hint that corresponds to the minimum size of the top level layout (\ref plotLayout). To prevent QCustomPlot from being collapsed to size/width zero, set a minimum size (setMinimumSize) either on the whole QCustomPlot or on any layout elements inside the plot. This is especially important, when placed in a QLayout where other components try to take in as much space as possible (e.g. QMdiArea). */ QSize QCustomPlot::minimumSizeHint() const { return mPlotLayout->minimumSizeHint(); } /*! \internal Returns a size hint that is the same as \ref minimumSizeHint. */ QSize QCustomPlot::sizeHint() const { return mPlotLayout->minimumSizeHint(); } /*! \internal Event handler for when the QCustomPlot widget needs repainting. This does not cause a \ref replot, but draws the internal buffer on the widget surface. */ void QCustomPlot::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QCPPainter painter(this); if (painter.isActive()) { painter.setRenderHint( QPainter::HighQualityAntialiasing); // to make Antialiasing look good if using the OpenGL graphicssystem if (mBackgroundBrush.style() != Qt::NoBrush) painter.fillRect(mViewport, mBackgroundBrush); drawBackground(&painter); for (int bufferIndex = 0; bufferIndex < mPaintBuffers.size(); ++bufferIndex) mPaintBuffers.at(bufferIndex)->draw(&painter); } } /*! \internal Event handler for a resize of the QCustomPlot widget. The viewport (which becomes the outer rect of mPlotLayout) is resized appropriately. Finally a \ref replot is performed. */ void QCustomPlot::resizeEvent(QResizeEvent *event) { Q_UNUSED(event) // resize and repaint the buffer: setViewport(rect()); replot( rpQueuedRefresh); // queued refresh is important here, to prevent painting issues in some contexts (e.g. MDI subwindow) } bool QCustomPlot::event(QEvent *event) { switch (event->type()) { case QEvent::Gesture: { QGestureEvent *gestureEve = static_cast(event); if (QGesture *pinch = gestureEve->gesture(Qt::PinchGesture)) { QPinchGesture *pinchEve = static_cast(pinch); qreal scaleFactor = pinchEve->totalScaleFactor(); if (scaleFactor > 1.0) { scaleFactor *= 5; } else { scaleFactor *= -15; } QWheelEvent *wheelEve = new QWheelEvent(this->mapFromGlobal(QCursor::pos()), scaleFactor, Qt::NoButton, Qt::NoModifier, Qt::Vertical); this->wheelEvent(wheelEve); } return true; } default: { break; } } return QWidget::event(event); } /*! \internal Event handler for when a double click occurs. Emits the \ref mouseDoubleClick signal, then determines the layerable under the cursor and forwards the event to it. Finally, emits the specialized signals when certain objecs are clicked (e.g. \ref plottableDoubleClick, \ref axisDoubleClick, etc.). \see mousePressEvent, mouseReleaseEvent */ void QCustomPlot::mouseDoubleClickEvent(QMouseEvent *event) { emit mouseDoubleClick(event); mMouseHasMoved = false; mMousePressPos = event->pos(); // determine layerable under the cursor (this event is called instead of the second press event in a double-click): QList details; QList candidates = layerableListAt(mMousePressPos, false, &details); for (int i = 0; i < candidates.size(); ++i) { event ->accept(); // default impl of QCPLayerable's mouse events ignore the event, in that case propagate to next candidate in list candidates.at(i)->mouseDoubleClickEvent(event, details.at(i)); if (event->isAccepted()) { mMouseEventLayerable = candidates.at(i); mMouseEventLayerableDetails = details.at(i); break; } } // emit specialized object double click signals: if (!candidates.isEmpty()) { if (QCPAbstractPlottable *ap = qobject_cast(candidates.first())) { int dataIndex = 0; if (!details.first().value().isEmpty()) dataIndex = details.first().value().dataRange().begin(); emit plottableDoubleClick(ap, dataIndex, event); } else if (QCPAxis *ax = qobject_cast(candidates.first())) emit axisDoubleClick(ax, details.first().value(), event); else if (QCPAbstractItem *ai = qobject_cast(candidates.first())) emit itemDoubleClick(ai, event); else if (QCPLegend *lg = qobject_cast(candidates.first())) - emit legendDoubleClick(lg, 0, event); + emit legendDoubleClick(lg, nullptr, event); else if (QCPAbstractLegendItem *li = qobject_cast(candidates.first())) emit legendDoubleClick(li->parentLegend(), li, event); } event ->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event. } /*! \internal Event handler for when a mouse button is pressed. Emits the mousePress signal. If the current \ref setSelectionRectMode is not \ref QCP::srmNone, passes the event to the selection rect. Otherwise determines the layerable under the cursor and forwards the event to it. \see mouseMoveEvent, mouseReleaseEvent */ void QCustomPlot::mousePressEvent(QMouseEvent *event) { emit mousePress(event); // save some state to tell in releaseEvent whether it was a click: mMouseHasMoved = false; mMousePressPos = event->pos(); if (mSelectionRect && mSelectionRectMode != QCP::srmNone) { if (mSelectionRectMode != QCP::srmZoom || qobject_cast( axisRectAt(mMousePressPos))) // in zoom mode only activate selection rect if on an axis rect mSelectionRect->startSelection(event); } else { // no selection rect interaction, so forward event to layerable under the cursor: QList details; QList candidates = layerableListAt(mMousePressPos, false, &details); for (int i = 0; i < candidates.size(); ++i) { event ->accept(); // default impl of QCPLayerable's mouse events ignore the event, in that case propagate to next candidate in list candidates.at(i)->mousePressEvent(event, details.at(i)); if (event->isAccepted()) { mMouseEventLayerable = candidates.at(i); mMouseEventLayerableDetails = details.at(i); break; } } } event ->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event. } /*! \internal Event handler for when the cursor is moved. Emits the \ref mouseMove signal. If the selection rect (\ref setSelectionRect) is currently active, the event is forwarded to it in order to update the rect geometry. Otherwise, if a layout element has mouse capture focus (a mousePressEvent happened on top of the layout element before), the mouseMoveEvent is forwarded to that element. \see mousePressEvent, mouseReleaseEvent */ void QCustomPlot::mouseMoveEvent(QMouseEvent *event) { emit mouseMove(event); if (!mMouseHasMoved && (mMousePressPos - event->pos()).manhattanLength() > 3) mMouseHasMoved = true; // moved too far from mouse press position, don't handle as click on mouse release if (mSelectionRect && mSelectionRect->isActive()) mSelectionRect->moveSelection(event); else if (mMouseEventLayerable) // call event of affected layerable: mMouseEventLayerable->mouseMoveEvent(event, mMousePressPos); event ->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event. } /*! \internal Event handler for when a mouse button is released. Emits the \ref mouseRelease signal. If the mouse was moved less than a certain threshold in any direction since the \ref mousePressEvent, it is considered a click which causes the selection mechanism (if activated via \ref setInteractions) to possibly change selection states accordingly. Further, specialized mouse click signals are emitted (e.g. \ref plottableClick, \ref axisClick, etc.) If a layerable is the mouse capturer (a \ref mousePressEvent happened on top of the layerable before), the \ref mouseReleaseEvent is forwarded to that element. \see mousePressEvent, mouseMoveEvent */ void QCustomPlot::mouseReleaseEvent(QMouseEvent *event) { emit mouseRelease(event); if (!mMouseHasMoved) // mouse hasn't moved (much) between press and release, so handle as click { if (mSelectionRect && mSelectionRect ->isActive()) // a simple click shouldn't successfully finish a selection rect, so cancel it here mSelectionRect->cancel(); if (event->button() == Qt::LeftButton) processPointSelection(event); // emit specialized click signals of QCustomPlot instance: if (QCPAbstractPlottable *ap = qobject_cast(mMouseEventLayerable)) { int dataIndex = 0; if (!mMouseEventLayerableDetails.value().isEmpty()) dataIndex = mMouseEventLayerableDetails.value().dataRange().begin(); emit plottableClick(ap, dataIndex, event); } else if (QCPAxis *ax = qobject_cast(mMouseEventLayerable)) emit axisClick(ax, mMouseEventLayerableDetails.value(), event); else if (QCPAbstractItem *ai = qobject_cast(mMouseEventLayerable)) emit itemClick(ai, event); else if (QCPLegend *lg = qobject_cast(mMouseEventLayerable)) - emit legendClick(lg, 0, event); + emit legendClick(lg, nullptr, event); else if (QCPAbstractLegendItem *li = qobject_cast(mMouseEventLayerable)) emit legendClick(li->parentLegend(), li, event); } if (mSelectionRect && mSelectionRect->isActive()) // Note: if a click was detected above, the selection rect is canceled there { // finish selection rect, the appropriate action will be taken via signal-slot connection: mSelectionRect->endSelection(event); } else { // call event of affected layerable: if (mMouseEventLayerable) { mMouseEventLayerable->mouseReleaseEvent(event, mMousePressPos); - mMouseEventLayerable = 0; + mMouseEventLayerable = nullptr; } } if (noAntialiasingOnDrag()) replot(rpQueuedReplot); event ->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event. } /*! \internal Event handler for mouse wheel events. First, the \ref mouseWheel signal is emitted. Then determines the affected layerable and forwards the event to it. */ void QCustomPlot::wheelEvent(QWheelEvent *event) { emit mouseWheel(event); // forward event to layerable under cursor: QList candidates = layerableListAt(event->pos(), false); for (int i = 0; i < candidates.size(); ++i) { event ->accept(); // default impl of QCPLayerable's mouse events ignore the event, in that case propagate to next candidate in list candidates.at(i)->wheelEvent(event); if (event->isAccepted()) break; } event ->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event. } /*! \internal This function draws the entire plot, including background pixmap, with the specified \a painter. It does not make use of the paint buffers like \ref replot, so this is the function typically used by saving/exporting methods such as \ref savePdf or \ref toPainter. Note that it does not fill the background with the background brush (as the user may specify with \ref setBackground(const QBrush &brush)), this is up to the respective functions calling this method. */ void QCustomPlot::draw(QCPPainter *painter) { updateLayout(); // draw viewport background pixmap: drawBackground(painter); // draw all layered objects (grid, axes, plottables, items, legend,...): foreach (QCPLayer *layer, mLayers) layer->draw(painter); /* Debug code to draw all layout element rects foreach (QCPLayoutElement* el, findChildren()) { painter->setBrush(Qt::NoBrush); painter->setPen(QPen(QColor(0, 0, 0, 100), 0, Qt::DashLine)); painter->drawRect(el->rect()); painter->setPen(QPen(QColor(255, 0, 0, 100), 0, Qt::DashLine)); painter->drawRect(el->outerRect()); } */ } /*! \internal Performs the layout update steps defined by \ref QCPLayoutElement::UpdatePhase, by calling \ref QCPLayoutElement::update on the main plot layout. Here, the layout elements calculate their positions and margins, and prepare for the following draw call. */ void QCustomPlot::updateLayout() { // run through layout phases: mPlotLayout->update(QCPLayoutElement::upPreparation); mPlotLayout->update(QCPLayoutElement::upMargins); mPlotLayout->update(QCPLayoutElement::upLayout); } /*! \internal Draws the viewport background pixmap of the plot. If a pixmap was provided via \ref setBackground, this function buffers the scaled version depending on \ref setBackgroundScaled and \ref setBackgroundScaledMode and then draws it inside the viewport with the provided \a painter. The scaled version is buffered in mScaledBackgroundPixmap to prevent expensive rescaling at every redraw. It is only updated, when the axis rect has changed in a way that requires a rescale of the background pixmap (this is dependent on the \ref setBackgroundScaledMode), or when a differend axis background pixmap was set. Note that this function does not draw a fill with the background brush (\ref setBackground(const QBrush &brush)) beneath the pixmap. \see setBackground, setBackgroundScaled, setBackgroundScaledMode */ void QCustomPlot::drawBackground(QCPPainter *painter) { // Note: background color is handled in individual replot/save functions // draw background pixmap (on top of fill, if brush specified): if (!mBackgroundPixmap.isNull()) { if (mBackgroundScaled) { // check whether mScaledBackground needs to be updated: QSize scaledSize(mBackgroundPixmap.size()); scaledSize.scale(mViewport.size(), mBackgroundScaledMode); if (mScaledBackgroundPixmap.size() != scaledSize) mScaledBackgroundPixmap = mBackgroundPixmap.scaled(mViewport.size(), mBackgroundScaledMode, Qt::SmoothTransformation); painter->drawPixmap(mViewport.topLeft(), mScaledBackgroundPixmap, QRect(0, 0, mViewport.width(), mViewport.height()) & mScaledBackgroundPixmap.rect()); } else { painter->drawPixmap(mViewport.topLeft(), mBackgroundPixmap, QRect(0, 0, mViewport.width(), mViewport.height())); } } } /*! \internal Goes through the layers and makes sure this QCustomPlot instance holds the correct number of paint buffers and that they have the correct configuration (size, pixel ratio, etc.). Allocations, reallocations and deletions of paint buffers are performed as necessary. It also associates the paint buffers with the layers, so they draw themselves into the right buffer when \ref QCPLayer::drawToPaintBuffer is called. This means it associates adjacent \ref QCPLayer::lmLogical layers to a mutual paint buffer and creates dedicated paint buffers for layers in \ref QCPLayer::lmBuffered mode. This method uses \ref createPaintBuffer to create new paint buffers. After this method, the paint buffers are empty (filled with \c Qt::transparent) and invalidated (so an attempt to replot only a single buffered layer causes a full replot). This method is called in every \ref replot call, prior to actually drawing the layers (into their associated paint buffer). If the paint buffers don't need changing/reallocating, this method basically leaves them alone and thus finishes very fast. */ void QCustomPlot::setupPaintBuffers() { int bufferIndex = 0; if (mPaintBuffers.isEmpty()) mPaintBuffers.append(QSharedPointer(createPaintBuffer())); for (int layerIndex = 0; layerIndex < mLayers.size(); ++layerIndex) { QCPLayer *layer = mLayers.at(layerIndex); if (layer->mode() == QCPLayer::lmLogical) { layer->mPaintBuffer = mPaintBuffers.at(bufferIndex).toWeakRef(); } else if (layer->mode() == QCPLayer::lmBuffered) { ++bufferIndex; if (bufferIndex >= mPaintBuffers.size()) mPaintBuffers.append(QSharedPointer(createPaintBuffer())); layer->mPaintBuffer = mPaintBuffers.at(bufferIndex).toWeakRef(); if (layerIndex < mLayers.size() - 1 && mLayers.at(layerIndex + 1)->mode() == QCPLayer:: lmLogical) // not last layer, and next one is logical, so prepare another buffer for next layerables { ++bufferIndex; if (bufferIndex >= mPaintBuffers.size()) mPaintBuffers.append(QSharedPointer(createPaintBuffer())); } } } // remove unneeded buffers: while (mPaintBuffers.size() - 1 > bufferIndex) mPaintBuffers.removeLast(); // resize buffers to viewport size and clear contents: for (int i = 0; i < mPaintBuffers.size(); ++i) { mPaintBuffers.at(i)->setSize(viewport().size()); // won't do anything if already correct size mPaintBuffers.at(i)->clear(Qt::transparent); mPaintBuffers.at(i)->setInvalidated(); } } /*! \internal This method is used by \ref setupPaintBuffers when it needs to create new paint buffers. Depending on the current setting of \ref setOpenGl, and the current Qt version, different backends (subclasses of \ref QCPAbstractPaintBuffer) are created, initialized with the proper size and device pixel ratio, and returned. */ QCPAbstractPaintBuffer *QCustomPlot::createPaintBuffer() { if (mOpenGl) { #if defined(QCP_OPENGL_FBO) return new QCPPaintBufferGlFbo(viewport().size(), mBufferDevicePixelRatio, mGlContext, mGlPaintDevice); #elif defined(QCP_OPENGL_PBUFFER) return new QCPPaintBufferGlPbuffer(viewport().size(), mBufferDevicePixelRatio, mOpenGlMultisamples); #else qDebug() << Q_FUNC_INFO << "OpenGL enabled even though no support for it compiled in, this shouldn't have happened. Falling " "back to pixmap paint buffer."; return new QCPPaintBufferPixmap(viewport().size(), mBufferDevicePixelRatio); #endif } else return new QCPPaintBufferPixmap(viewport().size(), mBufferDevicePixelRatio); } /*! This method returns whether any of the paint buffers held by this QCustomPlot instance are invalidated. If any buffer is invalidated, a partial replot (\ref QCPLayer::replot) is not allowed and always causes a full replot (\ref QCustomPlot::replot) of all layers. This is the case when for example the layer order has changed, new layers were added, layers were removed, or layer modes were changed (\ref QCPLayer::setMode). \see QCPAbstractPaintBuffer::setInvalidated */ bool QCustomPlot::hasInvalidatedPaintBuffers() { for (int i = 0; i < mPaintBuffers.size(); ++i) { if (mPaintBuffers.at(i)->invalidated()) return true; } return false; } /*! \internal When \ref setOpenGl is set to true, this method is used to initialize OpenGL (create a context, surface, paint device). Returns true on success. If this method is successful, all paint buffers should be deleted and then reallocated by calling \ref setupPaintBuffers, so the OpenGL-based paint buffer subclasses (\ref QCPPaintBufferGlPbuffer, \ref QCPPaintBufferGlFbo) are used for subsequent replots. \see freeOpenGl */ bool QCustomPlot::setupOpenGl() { #ifdef QCP_OPENGL_FBO freeOpenGl(); QSurfaceFormat proposedSurfaceFormat; proposedSurfaceFormat.setSamples(mOpenGlMultisamples); #ifdef QCP_OPENGL_OFFSCREENSURFACE QOffscreenSurface *surface = new QOffscreenSurface; #else QWindow *surface = new QWindow; surface->setSurfaceType(QSurface::OpenGLSurface); #endif surface->setFormat(proposedSurfaceFormat); surface->create(); mGlSurface = QSharedPointer(surface); mGlContext = QSharedPointer(new QOpenGLContext); mGlContext->setFormat(mGlSurface->format()); if (!mGlContext->create()) { qDebug() << Q_FUNC_INFO << "Failed to create OpenGL context"; mGlContext.clear(); mGlSurface.clear(); return false; } if (!mGlContext->makeCurrent(mGlSurface.data())) // context needs to be current to create paint device { qDebug() << Q_FUNC_INFO << "Failed to make opengl context current"; mGlContext.clear(); mGlSurface.clear(); return false; } if (!QOpenGLFramebufferObject::hasOpenGLFramebufferObjects()) { qDebug() << Q_FUNC_INFO << "OpenGL of this system doesn't support frame buffer objects"; mGlContext.clear(); mGlSurface.clear(); return false; } mGlPaintDevice = QSharedPointer(new QOpenGLPaintDevice); return true; #elif defined(QCP_OPENGL_PBUFFER) return QGLFormat::hasOpenGL(); #else return false; #endif } /*! \internal When \ref setOpenGl is set to false, this method is used to deinitialize OpenGL (releases the context and frees resources). After OpenGL is disabled, all paint buffers should be deleted and then reallocated by calling \ref setupPaintBuffers, so the standard software rendering paint buffer subclass (\ref QCPPaintBufferPixmap) is used for subsequent replots. \see setupOpenGl */ void QCustomPlot::freeOpenGl() { #ifdef QCP_OPENGL_FBO mGlPaintDevice.clear(); mGlContext.clear(); mGlSurface.clear(); #endif } /*! \internal This method is used by \ref QCPAxisRect::removeAxis to report removed axes to the QCustomPlot so it may clear its QCustomPlot::xAxis, yAxis, xAxis2 and yAxis2 members accordingly. */ void QCustomPlot::axisRemoved(QCPAxis *axis) { if (xAxis == axis) - xAxis = 0; + xAxis = nullptr; if (xAxis2 == axis) - xAxis2 = 0; + xAxis2 = nullptr; if (yAxis == axis) - yAxis = 0; + yAxis = nullptr; if (yAxis2 == axis) - yAxis2 = 0; + yAxis2 = nullptr; // Note: No need to take care of range drag axes and range zoom axes, because they are stored in smart pointers } /*! \internal This method is used by the QCPLegend destructor to report legend removal to the QCustomPlot so it may clear its QCustomPlot::legend member accordingly. */ void QCustomPlot::legendRemoved(QCPLegend *legend) { if (this->legend == legend) - this->legend = 0; + this->legend = nullptr; } /*! \internal This slot is connected to the selection rect's \ref QCPSelectionRect::accepted signal when \ref setSelectionRectMode is set to \ref QCP::srmSelect. First, it determines which axis rect was the origin of the selection rect judging by the starting point of the selection. Then it goes through the plottables (\ref QCPAbstractPlottable1D to be precise) associated with that axis rect and finds the data points that are in \a rect. It does this by querying their \ref QCPAbstractPlottable1D::selectTestRect method. Then, the actual selection is done by calling the plottables' \ref QCPAbstractPlottable::selectEvent, placing the found selected data points in the \a details parameter as QVariant(\ref QCPDataSelection). All plottables that weren't touched by \a rect receive a \ref QCPAbstractPlottable::deselectEvent. \see processRectZoom */ void QCustomPlot::processRectSelection(QRect rect, QMouseEvent *event) { bool selectionStateChanged = false; if (mInteractions.testFlag(QCP::iSelectPlottables)) { QMap> potentialSelections; // map key is number of selected data points, so we have selections sorted by size QRectF rectF(rect.normalized()); if (QCPAxisRect *affectedAxisRect = axisRectAt(rectF.topLeft())) { // determine plottables that were hit by the rect and thus are candidates for selection: foreach (QCPAbstractPlottable *plottable, affectedAxisRect->plottables()) { if (QCPPlottableInterface1D *plottableInterface = plottable->interface1D()) { QCPDataSelection dataSel = plottableInterface->selectTestRect(rectF, true); if (!dataSel.isEmpty()) potentialSelections.insertMulti( dataSel.dataPointCount(), QPair(plottable, dataSel)); } } if (!mInteractions.testFlag(QCP::iMultiSelect)) { // only leave plottable with most selected points in map, since we will only select a single plottable: if (!potentialSelections.isEmpty()) { QMap>::iterator it = potentialSelections.begin(); while (it != potentialSelections.end() - 1) // erase all except last element it = potentialSelections.erase(it); } } bool additive = event->modifiers().testFlag(mMultiSelectModifier); // deselect all other layerables if not additive selection: if (!additive) { // emit deselection except to those plottables who will be selected afterwards: foreach (QCPLayer *layer, mLayers) { foreach (QCPLayerable *layerable, layer->children()) { if ((potentialSelections.isEmpty() || potentialSelections.constBegin()->first != layerable) && mInteractions.testFlag(layerable->selectionCategory())) { bool selChanged = false; layerable->deselectEvent(&selChanged); selectionStateChanged |= selChanged; } } } } // go through selections in reverse (largest selection first) and emit select events: QMap>::const_iterator it = potentialSelections.constEnd(); while (it != potentialSelections.constBegin()) { --it; if (mInteractions.testFlag(it.value().first->selectionCategory())) { bool selChanged = false; it.value().first->selectEvent(event, additive, QVariant::fromValue(it.value().second), &selChanged); selectionStateChanged |= selChanged; } } } } if (selectionStateChanged) { emit selectionChangedByUser(); replot(rpQueuedReplot); } else if (mSelectionRect) mSelectionRect->layer()->replot(); } /*! \internal This slot is connected to the selection rect's \ref QCPSelectionRect::accepted signal when \ref setSelectionRectMode is set to \ref QCP::srmZoom. It determines which axis rect was the origin of the selection rect judging by the starting point of the selection, and then zooms the axes defined via \ref QCPAxisRect::setRangeZoomAxes to the provided \a rect (see \ref QCPAxisRect::zoom). \see processRectSelection */ void QCustomPlot::processRectZoom(QRect rect, QMouseEvent *event) { Q_UNUSED(event) if (QCPAxisRect *axisRect = axisRectAt(rect.topLeft())) { QList affectedAxes = QList() << axisRect->rangeZoomAxes(Qt::Horizontal) << axisRect->rangeZoomAxes(Qt::Vertical); - affectedAxes.removeAll(static_cast(0)); + affectedAxes.removeAll(static_cast(nullptr)); axisRect->zoom(QRectF(rect), affectedAxes); } replot(rpQueuedReplot); // always replot to make selection rect disappear } /*! \internal This method is called when a simple left mouse click was detected on the QCustomPlot surface. It first determines the layerable that was hit by the click, and then calls its \ref QCPLayerable::selectEvent. All other layerables receive a QCPLayerable::deselectEvent (unless the multi-select modifier was pressed, see \ref setMultiSelectModifier). In this method the hit layerable is determined a second time using \ref layerableAt (after the one in \ref mousePressEvent), because we want \a onlySelectable set to true this time. This implies that the mouse event grabber (mMouseEventLayerable) may be a different one from the clicked layerable determined here. For example, if a non-selectable layerable is in front of a selectable layerable at the click position, the front layerable will receive mouse events but the selectable one in the back will receive the \ref QCPLayerable::selectEvent. \see processRectSelection, QCPLayerable::selectTest */ void QCustomPlot::processPointSelection(QMouseEvent *event) { QVariant details; QCPLayerable *clickedLayerable = layerableAt(event->pos(), true, &details); bool selectionStateChanged = false; bool additive = mInteractions.testFlag(QCP::iMultiSelect) && event->modifiers().testFlag(mMultiSelectModifier); // deselect all other layerables if not additive selection: if (!additive) { foreach (QCPLayer *layer, mLayers) { foreach (QCPLayerable *layerable, layer->children()) { if (layerable != clickedLayerable && mInteractions.testFlag(layerable->selectionCategory())) { bool selChanged = false; layerable->deselectEvent(&selChanged); selectionStateChanged |= selChanged; } } } } if (clickedLayerable && mInteractions.testFlag(clickedLayerable->selectionCategory())) { // a layerable was actually clicked, call its selectEvent: bool selChanged = false; clickedLayerable->selectEvent(event, additive, details, &selChanged); selectionStateChanged |= selChanged; } if (selectionStateChanged) { emit selectionChangedByUser(); replot(rpQueuedReplot); } } /*! \internal Registers the specified plottable with this QCustomPlot and, if \ref setAutoAddPlottableToLegend is enabled, adds it to the legend (QCustomPlot::legend). QCustomPlot takes ownership of the plottable. Returns true on success, i.e. when \a plottable isn't already in this plot and the parent plot of \a plottable is this QCustomPlot. This method is called automatically in the QCPAbstractPlottable base class constructor. */ bool QCustomPlot::registerPlottable(QCPAbstractPlottable *plottable) { if (mPlottables.contains(plottable)) { qDebug() << Q_FUNC_INFO << "plottable already added to this QCustomPlot:" << reinterpret_cast(plottable); return false; } if (plottable->parentPlot() != this) { qDebug() << Q_FUNC_INFO << "plottable not created with this QCustomPlot as parent:" << reinterpret_cast(plottable); return false; } mPlottables.append(plottable); // possibly add plottable to legend: if (mAutoAddPlottableToLegend) plottable->addToLegend(); if (!plottable ->layer()) // usually the layer is already set in the constructor of the plottable (via QCPLayerable constructor) plottable->setLayer(currentLayer()); return true; } /*! \internal In order to maintain the simplified graph interface of QCustomPlot, this method is called by the QCPGraph constructor to register itself with this QCustomPlot's internal graph list. Returns true on success, i.e. if \a graph is valid and wasn't already registered with this QCustomPlot. This graph specific registration happens in addition to the call to \ref registerPlottable by the QCPAbstractPlottable base class. */ bool QCustomPlot::registerGraph(QCPGraph *graph) { if (!graph) { qDebug() << Q_FUNC_INFO << "passed graph is zero"; return false; } if (mGraphs.contains(graph)) { qDebug() << Q_FUNC_INFO << "graph already registered with this QCustomPlot"; return false; } mGraphs.append(graph); return true; } /*! \internal Registers the specified item with this QCustomPlot. QCustomPlot takes ownership of the item. Returns true on success, i.e. when \a item wasn't already in the plot and the parent plot of \a item is this QCustomPlot. This method is called automatically in the QCPAbstractItem base class constructor. */ bool QCustomPlot::registerItem(QCPAbstractItem *item) { if (mItems.contains(item)) { qDebug() << Q_FUNC_INFO << "item already added to this QCustomPlot:" << reinterpret_cast(item); return false; } if (item->parentPlot() != this) { qDebug() << Q_FUNC_INFO << "item not created with this QCustomPlot as parent:" << reinterpret_cast(item); return false; } mItems.append(item); if (!item->layer()) // usually the layer is already set in the constructor of the item (via QCPLayerable constructor) item->setLayer(currentLayer()); return true; } /*! \internal Assigns all layers their index (QCPLayer::mIndex) in the mLayers list. This method is thus called after every operation that changes the layer indices, like layer removal, layer creation, layer moving. */ void QCustomPlot::updateLayerIndices() const { for (int i = 0; i < mLayers.size(); ++i) mLayers.at(i)->mIndex = i; } /*! \internal Returns the top-most layerable at pixel position \a pos. If \a onlySelectable is set to true, only those layerables that are selectable will be considered. (Layerable subclasses communicate their selectability via the QCPLayerable::selectTest method, by returning -1.) \a selectionDetails is an output parameter that contains selection specifics of the affected layerable. This is useful if the respective layerable shall be given a subsequent QCPLayerable::selectEvent (like in \ref mouseReleaseEvent). \a selectionDetails usually contains information about which part of the layerable was hit, in multi-part layerables (e.g. QCPAxis::SelectablePart). If the layerable is a plottable, \a selectionDetails contains a \ref QCPDataSelection instance with the single data point which is closest to \a pos. \see layerableListAt, layoutElementAt, axisRectAt */ QCPLayerable *QCustomPlot::layerableAt(const QPointF &pos, bool onlySelectable, QVariant *selectionDetails) const { QList details; - QList candidates = layerableListAt(pos, onlySelectable, selectionDetails ? &details : 0); + QList candidates = layerableListAt(pos, onlySelectable, selectionDetails ? &details : nullptr); if (selectionDetails && !details.isEmpty()) *selectionDetails = details.first(); if (!candidates.isEmpty()) return candidates.first(); else - return 0; + return nullptr; } /*! \internal Returns the layerables at pixel position \a pos. If \a onlySelectable is set to true, only those layerables that are selectable will be considered. (Layerable subclasses communicate their selectability via the QCPLayerable::selectTest method, by returning -1.) The returned list is sorted by the layerable/drawing order. If you only need to know the top-most layerable, rather use \ref layerableAt. \a selectionDetails is an output parameter that contains selection specifics of the affected layerable. This is useful if the respective layerable shall be given a subsequent QCPLayerable::selectEvent (like in \ref mouseReleaseEvent). \a selectionDetails usually contains information about which part of the layerable was hit, in multi-part layerables (e.g. QCPAxis::SelectablePart). If the layerable is a plottable, \a selectionDetails contains a \ref QCPDataSelection instance with the single data point which is closest to \a pos. \see layerableAt, layoutElementAt, axisRectAt */ QList QCustomPlot::layerableListAt(const QPointF &pos, bool onlySelectable, QList *selectionDetails) const { QList result; for (int layerIndex = mLayers.size() - 1; layerIndex >= 0; --layerIndex) { const QList layerables = mLayers.at(layerIndex)->children(); for (int i = layerables.size() - 1; i >= 0; --i) { if (!layerables.at(i)->realVisibility()) continue; QVariant details; - double dist = layerables.at(i)->selectTest(pos, onlySelectable, selectionDetails ? &details : 0); + double dist = layerables.at(i)->selectTest(pos, onlySelectable, selectionDetails ? &details : nullptr); if (dist >= 0 && dist < selectionTolerance()) { result.append(layerables.at(i)); if (selectionDetails) selectionDetails->append(details); } } } return result; } /*! Saves the plot to a rastered image file \a fileName in the image format \a format. The plot is sized to \a width and \a height in pixels and scaled with \a scale. (width 100 and scale 2.0 lead to a full resolution file with width 200.) If the \a format supports compression, \a quality may be between 0 and 100 to control it. Returns true on success. If this function fails, most likely the given \a format isn't supported by the system, see Qt docs about QImageWriter::supportedImageFormats(). The \a resolution will be written to the image file header (if the file format supports this) and has no direct consequence for the quality or the pixel size. However, if opening the image with a tool which respects the metadata, it will be able to scale the image to match either a given size in real units of length (inch, centimeters, etc.), or the target display DPI. You can specify in which units \a resolution is given, by setting \a resolutionUnit. The \a resolution is converted to the format's expected resolution unit internally. \see saveBmp, saveJpg, savePng, savePdf */ bool QCustomPlot::saveRastered(const QString &fileName, int width, int height, double scale, const char *format, int quality, int resolution, QCP::ResolutionUnit resolutionUnit) { QImage buffer = toPixmap(width, height, scale).toImage(); int dotsPerMeter = 0; switch (resolutionUnit) { case QCP::ruDotsPerMeter: dotsPerMeter = resolution; break; case QCP::ruDotsPerCentimeter: dotsPerMeter = resolution * 100; break; case QCP::ruDotsPerInch: dotsPerMeter = resolution / 0.0254; break; } buffer.setDotsPerMeterX( dotsPerMeter); // this is saved together with some image formats, e.g. PNG, and is relevant when opening image in other tools buffer.setDotsPerMeterY( dotsPerMeter); // this is saved together with some image formats, e.g. PNG, and is relevant when opening image in other tools if (!buffer.isNull()) return buffer.save(fileName, format, quality); else return false; } /*! Renders the plot to a pixmap and returns it. The plot is sized to \a width and \a height in pixels and scaled with \a scale. (width 100 and scale 2.0 lead to a full resolution pixmap with width 200.) \see toPainter, saveRastered, saveBmp, savePng, saveJpg, savePdf */ QPixmap QCustomPlot::toPixmap(int width, int height, double scale) { // this method is somewhat similar to toPainter. Change something here, and a change in toPainter might be necessary, too. int newWidth, newHeight; if (width == 0 || height == 0) { newWidth = this->width(); newHeight = this->height(); } else { newWidth = width; newHeight = height; } int scaledWidth = qRound(scale * newWidth); int scaledHeight = qRound(scale * newHeight); QPixmap result(scaledWidth, scaledHeight); result.fill(mBackgroundBrush.style() == Qt::SolidPattern ? mBackgroundBrush.color() : Qt::transparent); // if using non-solid pattern, make transparent now and draw brush pattern later QCPPainter painter; painter.begin(&result); if (painter.isActive()) { QRect oldViewport = viewport(); setViewport(QRect(0, 0, newWidth, newHeight)); painter.setMode(QCPPainter::pmNoCaching); if (!qFuzzyCompare(scale, 1.0)) { if (scale > 1.0) // for scale < 1 we always want cosmetic pens where possible, because else lines might disappear for very small scales painter.setMode(QCPPainter::pmNonCosmetic); painter.scale(scale, scale); } if (mBackgroundBrush.style() != Qt::SolidPattern && mBackgroundBrush.style() != Qt::NoBrush) // solid fills were done a few lines above with QPixmap::fill painter.fillRect(mViewport, mBackgroundBrush); draw(&painter); setViewport(oldViewport); painter.end(); } else // might happen if pixmap has width or height zero { qDebug() << Q_FUNC_INFO << "Couldn't activate painter on pixmap"; return QPixmap(); } return result; } /*! Renders the plot using the passed \a painter. The plot is sized to \a width and \a height in pixels. If the \a painter's scale is not 1.0, the resulting plot will appear scaled accordingly. \note If you are restricted to using a QPainter (instead of QCPPainter), create a temporary QPicture and open a QCPPainter on it. Then call \ref toPainter with this QCPPainter. After ending the paint operation on the picture, draw it with the QPainter. This will reproduce the painter actions the QCPPainter took, with a QPainter. \see toPixmap */ void QCustomPlot::toPainter(QCPPainter *painter, int width, int height) { // this method is somewhat similar to toPixmap. Change something here, and a change in toPixmap might be necessary, too. int newWidth, newHeight; if (width == 0 || height == 0) { newWidth = this->width(); newHeight = this->height(); } else { newWidth = width; newHeight = height; } if (painter->isActive()) { QRect oldViewport = viewport(); setViewport(QRect(0, 0, newWidth, newHeight)); painter->setMode(QCPPainter::pmNoCaching); if (mBackgroundBrush.style() != Qt::NoBrush) // unlike in toPixmap, we can't do QPixmap::fill for Qt::SolidPattern brush style, so we also draw solid fills with fillRect here painter->fillRect(mViewport, mBackgroundBrush); draw(painter); setViewport(oldViewport); } else qDebug() << Q_FUNC_INFO << "Passed painter is not active"; } /* end of 'src/core.cpp' */ //amalgamation: add plottable1d.cpp /* including file 'src/colorgradient.cpp', size 24646 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPColorGradient //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPColorGradient \brief Defines a color gradient for use with e.g. \ref QCPColorMap This class describes a color gradient which can be used to encode data with color. For example, QCPColorMap and QCPColorScale have \ref QCPColorMap::setGradient "setGradient" methods which take an instance of this class. Colors are set with \ref setColorStopAt(double position, const QColor &color) with a \a position from 0 to 1. In between these defined color positions, the color will be interpolated linearly either in RGB or HSV space, see \ref setColorInterpolation. Alternatively, load one of the preset color gradients shown in the image below, with \ref loadPreset, or by directly specifying the preset in the constructor. Apart from red, green and blue components, the gradient also interpolates the alpha values of the configured color stops. This allows to display some portions of the data range as transparent in the plot. \image html QCPColorGradient.png The \ref QCPColorGradient(GradientPreset preset) constructor allows directly converting a \ref GradientPreset to a QCPColorGradient. This means that you can directly pass \ref GradientPreset to all the \a setGradient methods, e.g.: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolorgradient-setgradient The total number of levels used in the gradient can be set with \ref setLevelCount. Whether the color gradient shall be applied periodically (wrapping around) to data values that lie outside the data range specified on the plottable instance can be controlled with \ref setPeriodic. */ /*! Constructs a new, empty QCPColorGradient with no predefined color stops. You can add own color stops with \ref setColorStopAt. The color level count is initialized to 350. */ QCPColorGradient::QCPColorGradient() : mLevelCount(350), mColorInterpolation(ciRGB), mPeriodic(false), mColorBufferInvalidated(true) { mColorBuffer.fill(qRgb(0, 0, 0), mLevelCount); } /*! Constructs a new QCPColorGradient initialized with the colors and color interpolation according to \a preset. The color level count is initialized to 350. */ QCPColorGradient::QCPColorGradient(GradientPreset preset) : mLevelCount(350), mColorInterpolation(ciRGB), mPeriodic(false), mColorBufferInvalidated(true) { mColorBuffer.fill(qRgb(0, 0, 0), mLevelCount); loadPreset(preset); } /* undocumented operator */ bool QCPColorGradient::operator==(const QCPColorGradient &other) const { return ((other.mLevelCount == this->mLevelCount) && (other.mColorInterpolation == this->mColorInterpolation) && (other.mPeriodic == this->mPeriodic) && (other.mColorStops == this->mColorStops)); } /*! Sets the number of discretization levels of the color gradient to \a n. The default is 350 which is typically enough to create a smooth appearance. The minimum number of levels is 2. \image html QCPColorGradient-levelcount.png */ void QCPColorGradient::setLevelCount(int n) { if (n < 2) { qDebug() << Q_FUNC_INFO << "n must be greater or equal 2 but was" << n; n = 2; } if (n != mLevelCount) { mLevelCount = n; mColorBufferInvalidated = true; } } /*! Sets at which positions from 0 to 1 which color shall occur. The positions are the keys, the colors are the values of the passed QMap \a colorStops. In between these color stops, the color is interpolated according to \ref setColorInterpolation. A more convenient way to create a custom gradient may be to clear all color stops with \ref clearColorStops (or creating a new, empty QCPColorGradient) and then adding them one by one with \ref setColorStopAt. \see clearColorStops */ void QCPColorGradient::setColorStops(const QMap &colorStops) { mColorStops = colorStops; mColorBufferInvalidated = true; } /*! Sets the \a color the gradient will have at the specified \a position (from 0 to 1). In between these color stops, the color is interpolated according to \ref setColorInterpolation. \see setColorStops, clearColorStops */ void QCPColorGradient::setColorStopAt(double position, const QColor &color) { mColorStops.insert(position, color); mColorBufferInvalidated = true; } /*! Sets whether the colors in between the configured color stops (see \ref setColorStopAt) shall be interpolated linearly in RGB or in HSV color space. For example, a sweep in RGB space from red to green will have a muddy brown intermediate color, whereas in HSV space the intermediate color is yellow. */ void QCPColorGradient::setColorInterpolation(QCPColorGradient::ColorInterpolation interpolation) { if (interpolation != mColorInterpolation) { mColorInterpolation = interpolation; mColorBufferInvalidated = true; } } /*! Sets whether data points that are outside the configured data range (e.g. \ref QCPColorMap::setDataRange) are colored by periodically repeating the color gradient or whether they all have the same color, corresponding to the respective gradient boundary color. \image html QCPColorGradient-periodic.png As shown in the image above, gradients that have the same start and end color are especially suitable for a periodic gradient mapping, since they produce smooth color transitions throughout the color map. A preset that has this property is \ref gpHues. In practice, using periodic color gradients makes sense when the data corresponds to a periodic dimension, such as an angle or a phase. If this is not the case, the color encoding might become ambiguous, because multiple different data values are shown as the same color. */ void QCPColorGradient::setPeriodic(bool enabled) { mPeriodic = enabled; } /*! \overload This method is used to quickly convert a \a data array to colors. The colors will be output in the array \a scanLine. Both \a data and \a scanLine must have the length \a n when passed to this function. The data range that shall be used for mapping the data value to the gradient is passed in \a range. \a logarithmic indicates whether the data values shall be mapped to colors logarithmically. if \a data actually contains 2D-data linearized via [row*columnCount + column], you can set \a dataIndexFactor to columnCount to convert a column instead of a row of the data array, in \a scanLine. \a scanLine will remain a regular (1D) array. This works because \a data is addressed data[i*dataIndexFactor]. Use the overloaded method to additionally provide alpha map data. The QRgb values that are placed in \a scanLine have their r, g and b components premultiplied with alpha (see QImage::Format_ARGB32_Premultiplied). */ void QCPColorGradient::colorize(const double *data, const QCPRange &range, QRgb *scanLine, int n, int dataIndexFactor, bool logarithmic) { // If you change something here, make sure to also adapt color() and the other colorize() overload if (!data) { qDebug() << Q_FUNC_INFO << "null pointer given as data"; return; } if (!scanLine) { qDebug() << Q_FUNC_INFO << "null pointer given as scanLine"; return; } if (mColorBufferInvalidated) updateColorBuffer(); if (!logarithmic) { const double posToIndexFactor = (mLevelCount - 1) / range.size(); if (mPeriodic) { for (int i = 0; i < n; ++i) { int index = (int)((data[dataIndexFactor * i] - range.lower) * posToIndexFactor) % mLevelCount; if (index < 0) index += mLevelCount; scanLine[i] = mColorBuffer.at(index); } } else { for (int i = 0; i < n; ++i) { int index = (data[dataIndexFactor * i] - range.lower) * posToIndexFactor; if (index < 0) index = 0; else if (index >= mLevelCount) index = mLevelCount - 1; scanLine[i] = mColorBuffer.at(index); } } } else // logarithmic == true { if (mPeriodic) { for (int i = 0; i < n; ++i) { int index = (int)(qLn(data[dataIndexFactor * i] / range.lower) / qLn(range.upper / range.lower) * (mLevelCount - 1)) % mLevelCount; if (index < 0) index += mLevelCount; scanLine[i] = mColorBuffer.at(index); } } else { for (int i = 0; i < n; ++i) { int index = qLn(data[dataIndexFactor * i] / range.lower) / qLn(range.upper / range.lower) * (mLevelCount - 1); if (index < 0) index = 0; else if (index >= mLevelCount) index = mLevelCount - 1; scanLine[i] = mColorBuffer.at(index); } } } } /*! \overload Additionally to the other overload of \ref colorize, this method takes the array \a alpha, which has the same size and structure as \a data and encodes the alpha information per data point. The QRgb values that are placed in \a scanLine have their r, g and b components premultiplied with alpha (see QImage::Format_ARGB32_Premultiplied). */ void QCPColorGradient::colorize(const double *data, const unsigned char *alpha, const QCPRange &range, QRgb *scanLine, int n, int dataIndexFactor, bool logarithmic) { // If you change something here, make sure to also adapt color() and the other colorize() overload if (!data) { qDebug() << Q_FUNC_INFO << "null pointer given as data"; return; } if (!alpha) { qDebug() << Q_FUNC_INFO << "null pointer given as alpha"; return; } if (!scanLine) { qDebug() << Q_FUNC_INFO << "null pointer given as scanLine"; return; } if (mColorBufferInvalidated) updateColorBuffer(); if (!logarithmic) { const double posToIndexFactor = (mLevelCount - 1) / range.size(); if (mPeriodic) { for (int i = 0; i < n; ++i) { int index = (int)((data[dataIndexFactor * i] - range.lower) * posToIndexFactor) % mLevelCount; if (index < 0) index += mLevelCount; if (alpha[dataIndexFactor * i] == 255) { scanLine[i] = mColorBuffer.at(index); } else { const QRgb rgb = mColorBuffer.at(index); const float alphaF = alpha[dataIndexFactor * i] / 255.0f; scanLine[i] = qRgba(qRed(rgb) * alphaF, qGreen(rgb) * alphaF, qBlue(rgb) * alphaF, qAlpha(rgb) * alphaF); } } } else { for (int i = 0; i < n; ++i) { int index = (data[dataIndexFactor * i] - range.lower) * posToIndexFactor; if (index < 0) index = 0; else if (index >= mLevelCount) index = mLevelCount - 1; if (alpha[dataIndexFactor * i] == 255) { scanLine[i] = mColorBuffer.at(index); } else { const QRgb rgb = mColorBuffer.at(index); const float alphaF = alpha[dataIndexFactor * i] / 255.0f; scanLine[i] = qRgba(qRed(rgb) * alphaF, qGreen(rgb) * alphaF, qBlue(rgb) * alphaF, qAlpha(rgb) * alphaF); } } } } else // logarithmic == true { if (mPeriodic) { for (int i = 0; i < n; ++i) { int index = (int)(qLn(data[dataIndexFactor * i] / range.lower) / qLn(range.upper / range.lower) * (mLevelCount - 1)) % mLevelCount; if (index < 0) index += mLevelCount; if (alpha[dataIndexFactor * i] == 255) { scanLine[i] = mColorBuffer.at(index); } else { const QRgb rgb = mColorBuffer.at(index); const float alphaF = alpha[dataIndexFactor * i] / 255.0f; scanLine[i] = qRgba(qRed(rgb) * alphaF, qGreen(rgb) * alphaF, qBlue(rgb) * alphaF, qAlpha(rgb) * alphaF); } } } else { for (int i = 0; i < n; ++i) { int index = qLn(data[dataIndexFactor * i] / range.lower) / qLn(range.upper / range.lower) * (mLevelCount - 1); if (index < 0) index = 0; else if (index >= mLevelCount) index = mLevelCount - 1; if (alpha[dataIndexFactor * i] == 255) { scanLine[i] = mColorBuffer.at(index); } else { const QRgb rgb = mColorBuffer.at(index); const float alphaF = alpha[dataIndexFactor * i] / 255.0f; scanLine[i] = qRgba(qRed(rgb) * alphaF, qGreen(rgb) * alphaF, qBlue(rgb) * alphaF, qAlpha(rgb) * alphaF); } } } } } /*! \internal This method is used to colorize a single data value given in \a position, to colors. The data range that shall be used for mapping the data value to the gradient is passed in \a range. \a logarithmic indicates whether the data value shall be mapped to a color logarithmically. If an entire array of data values shall be converted, rather use \ref colorize, for better performance. The returned QRgb has its r, g and b components premultiplied with alpha (see QImage::Format_ARGB32_Premultiplied). */ QRgb QCPColorGradient::color(double position, const QCPRange &range, bool logarithmic) { // If you change something here, make sure to also adapt ::colorize() if (mColorBufferInvalidated) updateColorBuffer(); int index = 0; if (!logarithmic) index = (position - range.lower) * (mLevelCount - 1) / range.size(); else index = qLn(position / range.lower) / qLn(range.upper / range.lower) * (mLevelCount - 1); if (mPeriodic) { index = index % mLevelCount; if (index < 0) index += mLevelCount; } else { if (index < 0) index = 0; else if (index >= mLevelCount) index = mLevelCount - 1; } return mColorBuffer.at(index); } /*! Clears the current color stops and loads the specified \a preset. A preset consists of predefined color stops and the corresponding color interpolation method. The available presets are: \image html QCPColorGradient.png */ void QCPColorGradient::loadPreset(GradientPreset preset) { clearColorStops(); switch (preset) { case gpGrayscale: setColorInterpolation(ciRGB); setColorStopAt(0, Qt::black); setColorStopAt(1, Qt::white); break; case gpHot: setColorInterpolation(ciRGB); setColorStopAt(0, QColor(50, 0, 0)); setColorStopAt(0.2, QColor(180, 10, 0)); setColorStopAt(0.4, QColor(245, 50, 0)); setColorStopAt(0.6, QColor(255, 150, 10)); setColorStopAt(0.8, QColor(255, 255, 50)); setColorStopAt(1, QColor(255, 255, 255)); break; case gpCold: setColorInterpolation(ciRGB); setColorStopAt(0, QColor(0, 0, 50)); setColorStopAt(0.2, QColor(0, 10, 180)); setColorStopAt(0.4, QColor(0, 50, 245)); setColorStopAt(0.6, QColor(10, 150, 255)); setColorStopAt(0.8, QColor(50, 255, 255)); setColorStopAt(1, QColor(255, 255, 255)); break; case gpNight: setColorInterpolation(ciHSV); setColorStopAt(0, QColor(10, 20, 30)); setColorStopAt(1, QColor(250, 255, 250)); break; case gpCandy: setColorInterpolation(ciHSV); setColorStopAt(0, QColor(0, 0, 255)); setColorStopAt(1, QColor(255, 250, 250)); break; case gpGeography: setColorInterpolation(ciRGB); setColorStopAt(0, QColor(70, 170, 210)); setColorStopAt(0.20, QColor(90, 160, 180)); setColorStopAt(0.25, QColor(45, 130, 175)); setColorStopAt(0.30, QColor(100, 140, 125)); setColorStopAt(0.5, QColor(100, 140, 100)); setColorStopAt(0.6, QColor(130, 145, 120)); setColorStopAt(0.7, QColor(140, 130, 120)); setColorStopAt(0.9, QColor(180, 190, 190)); setColorStopAt(1, QColor(210, 210, 230)); break; case gpIon: setColorInterpolation(ciHSV); setColorStopAt(0, QColor(50, 10, 10)); setColorStopAt(0.45, QColor(0, 0, 255)); setColorStopAt(0.8, QColor(0, 255, 255)); setColorStopAt(1, QColor(0, 255, 0)); break; case gpThermal: setColorInterpolation(ciRGB); setColorStopAt(0, QColor(0, 0, 50)); setColorStopAt(0.15, QColor(20, 0, 120)); setColorStopAt(0.33, QColor(200, 30, 140)); setColorStopAt(0.6, QColor(255, 100, 0)); setColorStopAt(0.85, QColor(255, 255, 40)); setColorStopAt(1, QColor(255, 255, 255)); break; case gpPolar: setColorInterpolation(ciRGB); setColorStopAt(0, QColor(50, 255, 255)); setColorStopAt(0.18, QColor(10, 70, 255)); setColorStopAt(0.28, QColor(10, 10, 190)); setColorStopAt(0.5, QColor(0, 0, 0)); setColorStopAt(0.72, QColor(190, 10, 10)); setColorStopAt(0.82, QColor(255, 70, 10)); setColorStopAt(1, QColor(255, 255, 50)); break; case gpSpectrum: setColorInterpolation(ciHSV); setColorStopAt(0, QColor(50, 0, 50)); setColorStopAt(0.15, QColor(0, 0, 255)); setColorStopAt(0.35, QColor(0, 255, 255)); setColorStopAt(0.6, QColor(255, 255, 0)); setColorStopAt(0.75, QColor(255, 30, 0)); setColorStopAt(1, QColor(50, 0, 0)); break; case gpJet: setColorInterpolation(ciRGB); setColorStopAt(0, QColor(0, 0, 100)); setColorStopAt(0.15, QColor(0, 50, 255)); setColorStopAt(0.35, QColor(0, 255, 255)); setColorStopAt(0.65, QColor(255, 255, 0)); setColorStopAt(0.85, QColor(255, 30, 0)); setColorStopAt(1, QColor(100, 0, 0)); break; case gpHues: setColorInterpolation(ciHSV); setColorStopAt(0, QColor(255, 0, 0)); setColorStopAt(1.0 / 3.0, QColor(0, 0, 255)); setColorStopAt(2.0 / 3.0, QColor(0, 255, 0)); setColorStopAt(1, QColor(255, 0, 0)); break; } } /*! Clears all color stops. \see setColorStops, setColorStopAt */ void QCPColorGradient::clearColorStops() { mColorStops.clear(); mColorBufferInvalidated = true; } /*! Returns an inverted gradient. The inverted gradient has all properties as this \ref QCPColorGradient, but the order of the color stops is inverted. \see setColorStops, setColorStopAt */ QCPColorGradient QCPColorGradient::inverted() const { QCPColorGradient result(*this); result.clearColorStops(); for (QMap::const_iterator it = mColorStops.constBegin(); it != mColorStops.constEnd(); ++it) result.setColorStopAt(1.0 - it.key(), it.value()); return result; } /*! \internal Returns true if the color gradient uses transparency, i.e. if any of the configured color stops has an alpha value below 255. */ bool QCPColorGradient::stopsUseAlpha() const { for (QMap::const_iterator it = mColorStops.constBegin(); it != mColorStops.constEnd(); ++it) { if (it.value().alpha() < 255) return true; } return false; } /*! \internal Updates the internal color buffer which will be used by \ref colorize and \ref color, to quickly convert positions to colors. This is where the interpolation between color stops is calculated. */ void QCPColorGradient::updateColorBuffer() { if (mColorBuffer.size() != mLevelCount) mColorBuffer.resize(mLevelCount); if (mColorStops.size() > 1) { double indexToPosFactor = 1.0 / (double)(mLevelCount - 1); const bool useAlpha = stopsUseAlpha(); for (int i = 0; i < mLevelCount; ++i) { double position = i * indexToPosFactor; QMap::const_iterator it = const_cast &>(mColorStops).lowerBound(position); if (it == mColorStops.constEnd()) // position is on or after last stop, use color of last stop { mColorBuffer[i] = (it - 1).value().rgba(); } else if (it == mColorStops.constBegin()) // position is on or before first stop, use color of first stop { mColorBuffer[i] = it.value().rgba(); } else // position is in between stops (or on an intermediate stop), interpolate color { QMap::const_iterator high = it; QMap::const_iterator low = it - 1; double t = (position - low.key()) / (high.key() - low.key()); // interpolation factor 0..1 switch (mColorInterpolation) { case ciRGB: { if (useAlpha) { const int alpha = (1 - t) * low.value().alpha() + t * high.value().alpha(); const float alphaPremultiplier = alpha / 255.0f; // since we use QImage::Format_ARGB32_Premultiplied mColorBuffer[i] = qRgba( ((1 - t) * low.value().red() + t * high.value().red()) * alphaPremultiplier, ((1 - t) * low.value().green() + t * high.value().green()) * alphaPremultiplier, ((1 - t) * low.value().blue() + t * high.value().blue()) * alphaPremultiplier, alpha); } else { mColorBuffer[i] = qRgb(((1 - t) * low.value().red() + t * high.value().red()), ((1 - t) * low.value().green() + t * high.value().green()), ((1 - t) * low.value().blue() + t * high.value().blue())); } break; } case ciHSV: { QColor lowHsv = low.value().toHsv(); QColor highHsv = high.value().toHsv(); double hue = 0; double hueDiff = highHsv.hueF() - lowHsv.hueF(); if (hueDiff > 0.5) hue = lowHsv.hueF() - t * (1.0 - hueDiff); else if (hueDiff < -0.5) hue = lowHsv.hueF() + t * (1.0 + hueDiff); else hue = lowHsv.hueF() + t * hueDiff; if (hue < 0) hue += 1.0; else if (hue >= 1.0) hue -= 1.0; if (useAlpha) { const QRgb rgb = QColor::fromHsvF(hue, (1 - t) * lowHsv.saturationF() + t * highHsv.saturationF(), (1 - t) * lowHsv.valueF() + t * highHsv.valueF()) .rgb(); const float alpha = (1 - t) * lowHsv.alphaF() + t * highHsv.alphaF(); mColorBuffer[i] = qRgba(qRed(rgb) * alpha, qGreen(rgb) * alpha, qBlue(rgb) * alpha, 255 * alpha); } else { mColorBuffer[i] = QColor::fromHsvF(hue, (1 - t) * lowHsv.saturationF() + t * highHsv.saturationF(), (1 - t) * lowHsv.valueF() + t * highHsv.valueF()) .rgb(); } break; } } } } } else if (mColorStops.size() == 1) { const QRgb rgb = mColorStops.constBegin().value().rgb(); const float alpha = mColorStops.constBegin().value().alphaF(); mColorBuffer.fill(qRgba(qRed(rgb) * alpha, qGreen(rgb) * alpha, qBlue(rgb) * alpha, 255 * alpha)); } else // mColorStops is empty, fill color buffer with black { mColorBuffer.fill(qRgb(0, 0, 0)); } mColorBufferInvalidated = false; } /* end of 'src/colorgradient.cpp' */ /* including file 'src/selectiondecorator-bracket.cpp', size 12313 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPSelectionDecoratorBracket //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPSelectionDecoratorBracket \brief A selection decorator which draws brackets around each selected data segment Additionally to the regular highlighting of selected segments via color, fill and scatter style, this \ref QCPSelectionDecorator subclass draws markers at the begin and end of each selected data segment of the plottable. The shape of the markers can be controlled with \ref setBracketStyle, \ref setBracketWidth and \ref setBracketHeight. The color/fill can be controlled with \ref setBracketPen and \ref setBracketBrush. To introduce custom bracket styles, it is only necessary to sublcass \ref QCPSelectionDecoratorBracket and reimplement \ref drawBracket. The rest will be managed by the base class. */ /*! Creates a new QCPSelectionDecoratorBracket instance with default values. */ QCPSelectionDecoratorBracket::QCPSelectionDecoratorBracket() : mBracketPen(QPen(Qt::black)), mBracketBrush(Qt::NoBrush), mBracketWidth(5), mBracketHeight(50), mBracketStyle(bsSquareBracket), mTangentToData(false), mTangentAverage(2) { } QCPSelectionDecoratorBracket::~QCPSelectionDecoratorBracket() { } /*! Sets the pen that will be used to draw the brackets at the beginning and end of each selected data segment. */ void QCPSelectionDecoratorBracket::setBracketPen(const QPen &pen) { mBracketPen = pen; } /*! Sets the brush that will be used to draw the brackets at the beginning and end of each selected data segment. */ void QCPSelectionDecoratorBracket::setBracketBrush(const QBrush &brush) { mBracketBrush = brush; } /*! Sets the width of the drawn bracket. The width dimension is always parallel to the key axis of the data, or the tangent direction of the current data slope, if \ref setTangentToData is enabled. */ void QCPSelectionDecoratorBracket::setBracketWidth(int width) { mBracketWidth = width; } /*! Sets the height of the drawn bracket. The height dimension is always perpendicular to the key axis of the data, or the tangent direction of the current data slope, if \ref setTangentToData is enabled. */ void QCPSelectionDecoratorBracket::setBracketHeight(int height) { mBracketHeight = height; } /*! Sets the shape that the bracket/marker will have. \see setBracketWidth, setBracketHeight */ void QCPSelectionDecoratorBracket::setBracketStyle(QCPSelectionDecoratorBracket::BracketStyle style) { mBracketStyle = style; } /*! Sets whether the brackets will be rotated such that they align with the slope of the data at the position that they appear in. For noisy data, it might be more visually appealing to average the slope over multiple data points. This can be configured via \ref setTangentAverage. */ void QCPSelectionDecoratorBracket::setTangentToData(bool enabled) { mTangentToData = enabled; } /*! Controls over how many data points the slope shall be averaged, when brackets shall be aligned with the data (if \ref setTangentToData is true). From the position of the bracket, \a pointCount points towards the selected data range will be taken into account. The smallest value of \a pointCount is 1, which is effectively equivalent to disabling \ref setTangentToData. */ void QCPSelectionDecoratorBracket::setTangentAverage(int pointCount) { mTangentAverage = pointCount; if (mTangentAverage < 1) mTangentAverage = 1; } /*! Draws the bracket shape with \a painter. The parameter \a direction is either -1 or 1 and indicates whether the bracket shall point to the left or the right (i.e. is a closing or opening bracket, respectively). The passed \a painter already contains all transformations that are necessary to position and rotate the bracket appropriately. Painting operations can be performed as if drawing upright brackets on flat data with horizontal key axis, with (0, 0) being the center of the bracket. If you wish to sublcass \ref QCPSelectionDecoratorBracket in order to provide custom bracket shapes (see \ref QCPSelectionDecoratorBracket::bsUserStyle), this is the method you should reimplement. */ void QCPSelectionDecoratorBracket::drawBracket(QCPPainter *painter, int direction) const { switch (mBracketStyle) { case bsSquareBracket: { painter->drawLine(QLineF(mBracketWidth * direction, -mBracketHeight * 0.5, 0, -mBracketHeight * 0.5)); painter->drawLine(QLineF(mBracketWidth * direction, mBracketHeight * 0.5, 0, mBracketHeight * 0.5)); painter->drawLine(QLineF(0, -mBracketHeight * 0.5, 0, mBracketHeight * 0.5)); break; } case bsHalfEllipse: { painter->drawArc(-mBracketWidth * 0.5, -mBracketHeight * 0.5, mBracketWidth, mBracketHeight, -90 * 16, -180 * 16 * direction); break; } case bsEllipse: { painter->drawEllipse(-mBracketWidth * 0.5, -mBracketHeight * 0.5, mBracketWidth, mBracketHeight); break; } case bsPlus: { painter->drawLine(QLineF(0, -mBracketHeight * 0.5, 0, mBracketHeight * 0.5)); painter->drawLine(QLineF(-mBracketWidth * 0.5, 0, mBracketWidth * 0.5, 0)); break; } default: { qDebug() << Q_FUNC_INFO << "unknown/custom bracket style can't be handeld by default implementation:" << static_cast(mBracketStyle); break; } } } /*! Draws the bracket decoration on the data points at the begin and end of each selected data segment given in \a seletion. It uses the method \ref drawBracket to actually draw the shapes. \seebaseclassmethod */ void QCPSelectionDecoratorBracket::drawDecoration(QCPPainter *painter, QCPDataSelection selection) { if (!mPlottable || selection.isEmpty()) return; if (QCPPlottableInterface1D *interface1d = mPlottable->interface1D()) { foreach (const QCPDataRange &dataRange, selection.dataRanges()) { // determine position and (if tangent mode is enabled) angle of brackets: int openBracketDir = (mPlottable->keyAxis() && !mPlottable->keyAxis()->rangeReversed()) ? 1 : -1; int closeBracketDir = -openBracketDir; QPointF openBracketPos = getPixelCoordinates(interface1d, dataRange.begin()); QPointF closeBracketPos = getPixelCoordinates(interface1d, dataRange.end() - 1); double openBracketAngle = 0; double closeBracketAngle = 0; if (mTangentToData) { openBracketAngle = getTangentAngle(interface1d, dataRange.begin(), openBracketDir); closeBracketAngle = getTangentAngle(interface1d, dataRange.end() - 1, closeBracketDir); } // draw opening bracket: QTransform oldTransform = painter->transform(); painter->setPen(mBracketPen); painter->setBrush(mBracketBrush); painter->translate(openBracketPos); painter->rotate(openBracketAngle / M_PI * 180.0); drawBracket(painter, openBracketDir); painter->setTransform(oldTransform); // draw closing bracket: painter->setPen(mBracketPen); painter->setBrush(mBracketBrush); painter->translate(closeBracketPos); painter->rotate(closeBracketAngle / M_PI * 180.0); drawBracket(painter, closeBracketDir); painter->setTransform(oldTransform); } } } /*! \internal If \ref setTangentToData is enabled, brackets need to be rotated according to the data slope. This method returns the angle in radians by which a bracket at the given \a dataIndex must be rotated. The parameter \a direction must be set to either -1 or 1, representing whether it is an opening or closing bracket. Since for slope calculation multiple data points are required, this defines the direction in which the algorithm walks, starting at \a dataIndex, to average those data points. (see \ref setTangentToData and \ref setTangentAverage) \a interface1d is the interface to the plottable's data which is used to query data coordinates. */ double QCPSelectionDecoratorBracket::getTangentAngle(const QCPPlottableInterface1D *interface1d, int dataIndex, int direction) const { if (!interface1d || dataIndex < 0 || dataIndex >= interface1d->dataCount()) return 0; direction = direction < 0 ? -1 : 1; // enforce direction is either -1 or 1 // how many steps we can actually go from index in the given direction without exceeding data bounds: int averageCount; if (direction < 0) averageCount = qMin(mTangentAverage, dataIndex); else averageCount = qMin(mTangentAverage, interface1d->dataCount() - 1 - dataIndex); qDebug() << averageCount; // calculate point average of averageCount points: QVector points(averageCount); QPointF pointsAverage; int currentIndex = dataIndex; for (int i = 0; i < averageCount; ++i) { points[i] = getPixelCoordinates(interface1d, currentIndex); pointsAverage += points[i]; currentIndex += direction; } pointsAverage /= (double)averageCount; // calculate slope of linear regression through points: double numSum = 0; double denomSum = 0; for (int i = 0; i < averageCount; ++i) { const double dx = points.at(i).x() - pointsAverage.x(); const double dy = points.at(i).y() - pointsAverage.y(); numSum += dx * dy; denomSum += dx * dx; } if (!qFuzzyIsNull(denomSum) && !qFuzzyIsNull(numSum)) { return qAtan2(numSum, denomSum); } else // undetermined angle, probably mTangentAverage == 1, so using only one data point return 0; } /*! \internal Returns the pixel coordinates of the data point at \a dataIndex, using \a interface1d to access the data points. */ QPointF QCPSelectionDecoratorBracket::getPixelCoordinates(const QCPPlottableInterface1D *interface1d, int dataIndex) const { QCPAxis *keyAxis = mPlottable->keyAxis(); QCPAxis *valueAxis = mPlottable->valueAxis(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPointF(0, 0); } if (keyAxis->orientation() == Qt::Horizontal) return QPointF(keyAxis->coordToPixel(interface1d->dataMainKey(dataIndex)), valueAxis->coordToPixel(interface1d->dataMainValue(dataIndex))); else return QPointF(valueAxis->coordToPixel(interface1d->dataMainValue(dataIndex)), keyAxis->coordToPixel(interface1d->dataMainKey(dataIndex))); } /* end of 'src/selectiondecorator-bracket.cpp' */ /* including file 'src/layoutelements/layoutelement-axisrect.cpp', size 47509 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxisRect //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPAxisRect \brief Holds multiple axes and arranges them in a rectangular shape. This class represents an axis rect, a rectangular area that is bounded on all sides with an arbitrary number of axes. Initially QCustomPlot has one axis rect, accessible via QCustomPlot::axisRect(). However, the layout system allows to have multiple axis rects, e.g. arranged in a grid layout (QCustomPlot::plotLayout). By default, QCPAxisRect comes with four axes, at bottom, top, left and right. They can be accessed via \ref axis by providing the respective axis type (\ref QCPAxis::AxisType) and index. If you need all axes in the axis rect, use \ref axes. The top and right axes are set to be invisible initially (QCPAxis::setVisible). To add more axes to a side, use \ref addAxis or \ref addAxes. To remove an axis, use \ref removeAxis. The axis rect layerable itself only draws a background pixmap or color, if specified (\ref setBackground). It is placed on the "background" layer initially (see \ref QCPLayer for an explanation of the QCustomPlot layer system). The axes that are held by the axis rect can be placed on other layers, independently of the axis rect. Every axis rect has a child layout of type \ref QCPLayoutInset. It is accessible via \ref insetLayout and can be used to have other layout elements (or even other layouts with multiple elements) hovering inside the axis rect. If an axis rect is clicked and dragged, it processes this by moving certain axis ranges. The behaviour can be controlled with \ref setRangeDrag and \ref setRangeDragAxes. If the mouse wheel is scrolled while the cursor is on the axis rect, certain axes are scaled. This is controllable via \ref setRangeZoom, \ref setRangeZoomAxes and \ref setRangeZoomFactor. These interactions are only enabled if \ref QCustomPlot::setInteractions contains \ref QCP::iRangeDrag and \ref QCP::iRangeZoom. \image html AxisRectSpacingOverview.png
Overview of the spacings and paddings that define the geometry of an axis. The dashed line on the far left indicates the viewport/widget border.
*/ /* start documentation of inline functions */ /*! \fn QCPLayoutInset *QCPAxisRect::insetLayout() const Returns the inset layout of this axis rect. It can be used to place other layout elements (or even layouts with multiple other elements) inside/on top of an axis rect. \see QCPLayoutInset */ /*! \fn int QCPAxisRect::left() const Returns the pixel position of the left border of this axis rect. Margins are not taken into account here, so the returned value is with respect to the inner \ref rect. */ /*! \fn int QCPAxisRect::right() const Returns the pixel position of the right border of this axis rect. Margins are not taken into account here, so the returned value is with respect to the inner \ref rect. */ /*! \fn int QCPAxisRect::top() const Returns the pixel position of the top border of this axis rect. Margins are not taken into account here, so the returned value is with respect to the inner \ref rect. */ /*! \fn int QCPAxisRect::bottom() const Returns the pixel position of the bottom border of this axis rect. Margins are not taken into account here, so the returned value is with respect to the inner \ref rect. */ /*! \fn int QCPAxisRect::width() const Returns the pixel width of this axis rect. Margins are not taken into account here, so the returned value is with respect to the inner \ref rect. */ /*! \fn int QCPAxisRect::height() const Returns the pixel height of this axis rect. Margins are not taken into account here, so the returned value is with respect to the inner \ref rect. */ /*! \fn QSize QCPAxisRect::size() const Returns the pixel size of this axis rect. Margins are not taken into account here, so the returned value is with respect to the inner \ref rect. */ /*! \fn QPoint QCPAxisRect::topLeft() const Returns the top left corner of this axis rect in pixels. Margins are not taken into account here, so the returned value is with respect to the inner \ref rect. */ /*! \fn QPoint QCPAxisRect::topRight() const Returns the top right corner of this axis rect in pixels. Margins are not taken into account here, so the returned value is with respect to the inner \ref rect. */ /*! \fn QPoint QCPAxisRect::bottomLeft() const Returns the bottom left corner of this axis rect in pixels. Margins are not taken into account here, so the returned value is with respect to the inner \ref rect. */ /*! \fn QPoint QCPAxisRect::bottomRight() const Returns the bottom right corner of this axis rect in pixels. Margins are not taken into account here, so the returned value is with respect to the inner \ref rect. */ /*! \fn QPoint QCPAxisRect::center() const Returns the center of this axis rect in pixels. Margins are not taken into account here, so the returned value is with respect to the inner \ref rect. */ /* end documentation of inline functions */ /*! Creates a QCPAxisRect instance and sets default values. An axis is added for each of the four sides, the top and right axes are set invisible initially. */ QCPAxisRect::QCPAxisRect(QCustomPlot *parentPlot, bool setupDefaultAxes) : QCPLayoutElement(parentPlot), mBackgroundBrush(Qt::NoBrush), mBackgroundScaled(true), mBackgroundScaledMode(Qt::KeepAspectRatioByExpanding), mInsetLayout(new QCPLayoutInset), mRangeDrag(Qt::Horizontal | Qt::Vertical), mRangeZoom(Qt::Horizontal | Qt::Vertical), mRangeZoomFactorHorz(0.85), mRangeZoomFactorVert(0.85), mDragging(false) { mInsetLayout->initializeParentPlot(mParentPlot); mInsetLayout->setParentLayerable(this); mInsetLayout->setParent(this); setMinimumSize(50, 50); setMinimumMargins(QMargins(15, 15, 15, 15)); mAxes.insert(QCPAxis::atLeft, QList()); mAxes.insert(QCPAxis::atRight, QList()); mAxes.insert(QCPAxis::atTop, QList()); mAxes.insert(QCPAxis::atBottom, QList()); if (setupDefaultAxes) { QCPAxis *xAxis = addAxis(QCPAxis::atBottom); QCPAxis *yAxis = addAxis(QCPAxis::atLeft); QCPAxis *xAxis2 = addAxis(QCPAxis::atTop); QCPAxis *yAxis2 = addAxis(QCPAxis::atRight); setRangeDragAxes(xAxis, yAxis); setRangeZoomAxes(xAxis, yAxis); xAxis2->setVisible(false); yAxis2->setVisible(false); xAxis->grid()->setVisible(true); yAxis->grid()->setVisible(true); xAxis2->grid()->setVisible(false); yAxis2->grid()->setVisible(false); xAxis2->grid()->setZeroLinePen(Qt::NoPen); yAxis2->grid()->setZeroLinePen(Qt::NoPen); xAxis2->grid()->setVisible(false); yAxis2->grid()->setVisible(false); } } QCPAxisRect::~QCPAxisRect() { delete mInsetLayout; - mInsetLayout = 0; + mInsetLayout = nullptr; QList axesList = axes(); for (int i = 0; i < axesList.size(); ++i) removeAxis(axesList.at(i)); } /*! Returns the number of axes on the axis rect side specified with \a type. \see axis */ int QCPAxisRect::axisCount(QCPAxis::AxisType type) const { return mAxes.value(type).size(); } /*! Returns the axis with the given \a index on the axis rect side specified with \a type. \see axisCount, axes */ QCPAxis *QCPAxisRect::axis(QCPAxis::AxisType type, int index) const { QList ax(mAxes.value(type)); if (index >= 0 && index < ax.size()) { return ax.at(index); } else { qDebug() << Q_FUNC_INFO << "Axis index out of bounds:" << index; - return 0; + return nullptr; } } /*! Returns all axes on the axis rect sides specified with \a types. \a types may be a single \ref QCPAxis::AxisType or an or-combination, to get the axes of multiple sides. \see axis */ QList QCPAxisRect::axes(QCPAxis::AxisTypes types) const { QList result; if (types.testFlag(QCPAxis::atLeft)) result << mAxes.value(QCPAxis::atLeft); if (types.testFlag(QCPAxis::atRight)) result << mAxes.value(QCPAxis::atRight); if (types.testFlag(QCPAxis::atTop)) result << mAxes.value(QCPAxis::atTop); if (types.testFlag(QCPAxis::atBottom)) result << mAxes.value(QCPAxis::atBottom); return result; } /*! \overload Returns all axes of this axis rect. */ QList QCPAxisRect::axes() const { QList result; QHashIterator> it(mAxes); while (it.hasNext()) { it.next(); result << it.value(); } return result; } /*! Adds a new axis to the axis rect side specified with \a type, and returns it. If \a axis is 0, a new QCPAxis instance is created internally. QCustomPlot owns the returned axis, so if you want to remove an axis, use \ref removeAxis instead of deleting it manually. You may inject QCPAxis instances (or sublasses of QCPAxis) by setting \a axis to an axis that was previously created outside QCustomPlot. It is important to note that QCustomPlot takes ownership of the axis, so you may not delete it afterwards. Further, the \a axis must have been created with this axis rect as parent and with the same axis type as specified in \a type. If this is not the case, a debug output is generated, the axis is not added, and the method returns 0. This method can not be used to move \a axis between axis rects. The same \a axis instance must not be added multiple times to the same or different axis rects. If an axis rect side already contains one or more axes, the lower and upper endings of the new axis (\ref QCPAxis::setLowerEnding, \ref QCPAxis::setUpperEnding) are set to \ref QCPLineEnding::esHalfBar. \see addAxes, setupFullAxesBox */ QCPAxis *QCPAxisRect::addAxis(QCPAxis::AxisType type, QCPAxis *axis) { QCPAxis *newAxis = axis; if (!newAxis) { newAxis = new QCPAxis(this, type); } else // user provided existing axis instance, do some sanity checks { if (newAxis->axisType() != type) { qDebug() << Q_FUNC_INFO << "passed axis has different axis type than specified in type parameter"; - return 0; + return nullptr; } if (newAxis->axisRect() != this) { qDebug() << Q_FUNC_INFO << "passed axis doesn't have this axis rect as parent axis rect"; - return 0; + return nullptr; } if (axes().contains(newAxis)) { qDebug() << Q_FUNC_INFO << "passed axis is already owned by this axis rect"; - return 0; + return nullptr; } } if (mAxes[type].size() > 0) // multiple axes on one side, add half-bar axis ending to additional axes with offset { bool invert = (type == QCPAxis::atRight) || (type == QCPAxis::atBottom); newAxis->setLowerEnding(QCPLineEnding(QCPLineEnding::esHalfBar, 6, 10, !invert)); newAxis->setUpperEnding(QCPLineEnding(QCPLineEnding::esHalfBar, 6, 10, invert)); } mAxes[type].append(newAxis); // reset convenience axis pointers on parent QCustomPlot if they are unset: if (mParentPlot && mParentPlot->axisRectCount() > 0 && mParentPlot->axisRect(0) == this) { switch (type) { case QCPAxis::atBottom: { if (!mParentPlot->xAxis) mParentPlot->xAxis = newAxis; break; } case QCPAxis::atLeft: { if (!mParentPlot->yAxis) mParentPlot->yAxis = newAxis; break; } case QCPAxis::atTop: { if (!mParentPlot->xAxis2) mParentPlot->xAxis2 = newAxis; break; } case QCPAxis::atRight: { if (!mParentPlot->yAxis2) mParentPlot->yAxis2 = newAxis; break; } } } return newAxis; } /*! Adds a new axis with \ref addAxis to each axis rect side specified in \a types. This may be an or-combination of QCPAxis::AxisType, so axes can be added to multiple sides at once. Returns a list of the added axes. \see addAxis, setupFullAxesBox */ QList QCPAxisRect::addAxes(QCPAxis::AxisTypes types) { QList result; if (types.testFlag(QCPAxis::atLeft)) result << addAxis(QCPAxis::atLeft); if (types.testFlag(QCPAxis::atRight)) result << addAxis(QCPAxis::atRight); if (types.testFlag(QCPAxis::atTop)) result << addAxis(QCPAxis::atTop); if (types.testFlag(QCPAxis::atBottom)) result << addAxis(QCPAxis::atBottom); return result; } /*! Removes the specified \a axis from the axis rect and deletes it. Returns true on success, i.e. if \a axis was a valid axis in this axis rect. \see addAxis */ bool QCPAxisRect::removeAxis(QCPAxis *axis) { // don't access axis->axisType() to provide safety when axis is an invalid pointer, rather go through all axis containers: QHashIterator> it(mAxes); while (it.hasNext()) { it.next(); if (it.value().contains(axis)) { mAxes[it.key()].removeOne(axis); if (qobject_cast( parentPlot())) // make sure this isn't called from QObject dtor when QCustomPlot is already destructed (happens when the axis rect is not in any layout and thus QObject-child of QCustomPlot) parentPlot()->axisRemoved(axis); delete axis; return true; } } qDebug() << Q_FUNC_INFO << "Axis isn't in axis rect:" << reinterpret_cast(axis); return false; } /*! Zooms in (or out) to the passed rectangular region \a pixelRect, given in pixel coordinates. All axes of this axis rect will have their range zoomed accordingly. If you only wish to zoom specific axes, use the overloaded version of this method. \see QCustomPlot::setSelectionRectMode */ void QCPAxisRect::zoom(const QRectF &pixelRect) { zoom(pixelRect, axes()); } /*! \overload Zooms in (or out) to the passed rectangular region \a pixelRect, given in pixel coordinates. Only the axes passed in \a affectedAxes will have their ranges zoomed accordingly. \see QCustomPlot::setSelectionRectMode */ void QCPAxisRect::zoom(const QRectF &pixelRect, const QList &affectedAxes) { foreach (QCPAxis *axis, affectedAxes) { if (!axis) { qDebug() << Q_FUNC_INFO << "a passed axis was zero"; continue; } QCPRange pixelRange; if (axis->orientation() == Qt::Horizontal) pixelRange = QCPRange(pixelRect.left(), pixelRect.right()); else pixelRange = QCPRange(pixelRect.top(), pixelRect.bottom()); axis->setRange(axis->pixelToCoord(pixelRange.lower), axis->pixelToCoord(pixelRange.upper)); } } /*! Convenience function to create an axis on each side that doesn't have any axes yet and set their visibility to true. Further, the top/right axes are assigned the following properties of the bottom/left axes: \li range (\ref QCPAxis::setRange) \li range reversed (\ref QCPAxis::setRangeReversed) \li scale type (\ref QCPAxis::setScaleType) \li tick visibility (\ref QCPAxis::setTicks) \li number format (\ref QCPAxis::setNumberFormat) \li number precision (\ref QCPAxis::setNumberPrecision) \li tick count of ticker (\ref QCPAxisTicker::setTickCount) \li tick origin of ticker (\ref QCPAxisTicker::setTickOrigin) Tick label visibility (\ref QCPAxis::setTickLabels) of the right and top axes are set to false. If \a connectRanges is true, the \ref QCPAxis::rangeChanged "rangeChanged" signals of the bottom and left axes are connected to the \ref QCPAxis::setRange slots of the top and right axes. */ void QCPAxisRect::setupFullAxesBox(bool connectRanges) { QCPAxis *xAxis, *yAxis, *xAxis2, *yAxis2; if (axisCount(QCPAxis::atBottom) == 0) xAxis = addAxis(QCPAxis::atBottom); else xAxis = axis(QCPAxis::atBottom); if (axisCount(QCPAxis::atLeft) == 0) yAxis = addAxis(QCPAxis::atLeft); else yAxis = axis(QCPAxis::atLeft); if (axisCount(QCPAxis::atTop) == 0) xAxis2 = addAxis(QCPAxis::atTop); else xAxis2 = axis(QCPAxis::atTop); if (axisCount(QCPAxis::atRight) == 0) yAxis2 = addAxis(QCPAxis::atRight); else yAxis2 = axis(QCPAxis::atRight); xAxis->setVisible(true); yAxis->setVisible(true); xAxis2->setVisible(true); yAxis2->setVisible(true); xAxis2->setTickLabels(false); yAxis2->setTickLabels(false); xAxis2->setRange(xAxis->range()); xAxis2->setRangeReversed(xAxis->rangeReversed()); xAxis2->setScaleType(xAxis->scaleType()); xAxis2->setTicks(xAxis->ticks()); xAxis2->setNumberFormat(xAxis->numberFormat()); xAxis2->setNumberPrecision(xAxis->numberPrecision()); xAxis2->ticker()->setTickCount(xAxis->ticker()->tickCount()); xAxis2->ticker()->setTickOrigin(xAxis->ticker()->tickOrigin()); yAxis2->setRange(yAxis->range()); yAxis2->setRangeReversed(yAxis->rangeReversed()); yAxis2->setScaleType(yAxis->scaleType()); yAxis2->setTicks(yAxis->ticks()); yAxis2->setNumberFormat(yAxis->numberFormat()); yAxis2->setNumberPrecision(yAxis->numberPrecision()); yAxis2->ticker()->setTickCount(yAxis->ticker()->tickCount()); yAxis2->ticker()->setTickOrigin(yAxis->ticker()->tickOrigin()); if (connectRanges) { connect(xAxis, SIGNAL(rangeChanged(QCPRange)), xAxis2, SLOT(setRange(QCPRange))); connect(yAxis, SIGNAL(rangeChanged(QCPRange)), yAxis2, SLOT(setRange(QCPRange))); } } /*! Returns a list of all the plottables that are associated with this axis rect. A plottable is considered associated with an axis rect if its key or value axis (or both) is in this axis rect. \see graphs, items */ QList QCPAxisRect::plottables() const { // Note: don't append all QCPAxis::plottables() into a list, because we might get duplicate entries QList result; for (int i = 0; i < mParentPlot->mPlottables.size(); ++i) { if (mParentPlot->mPlottables.at(i)->keyAxis()->axisRect() == this || mParentPlot->mPlottables.at(i)->valueAxis()->axisRect() == this) result.append(mParentPlot->mPlottables.at(i)); } return result; } /*! Returns a list of all the graphs that are associated with this axis rect. A graph is considered associated with an axis rect if its key or value axis (or both) is in this axis rect. \see plottables, items */ QList QCPAxisRect::graphs() const { // Note: don't append all QCPAxis::graphs() into a list, because we might get duplicate entries QList result; for (int i = 0; i < mParentPlot->mGraphs.size(); ++i) { if (mParentPlot->mGraphs.at(i)->keyAxis()->axisRect() == this || mParentPlot->mGraphs.at(i)->valueAxis()->axisRect() == this) result.append(mParentPlot->mGraphs.at(i)); } return result; } /*! Returns a list of all the items that are associated with this axis rect. An item is considered associated with an axis rect if any of its positions has key or value axis set to an axis that is in this axis rect, or if any of its positions has \ref QCPItemPosition::setAxisRect set to the axis rect, or if the clip axis rect (\ref QCPAbstractItem::setClipAxisRect) is set to this axis rect. \see plottables, graphs */ QList QCPAxisRect::items() const { // Note: don't just append all QCPAxis::items() into a list, because we might get duplicate entries // and miss those items that have this axis rect as clipAxisRect. QList result; for (int itemId = 0; itemId < mParentPlot->mItems.size(); ++itemId) { if (mParentPlot->mItems.at(itemId)->clipAxisRect() == this) { result.append(mParentPlot->mItems.at(itemId)); continue; } QList positions = mParentPlot->mItems.at(itemId)->positions(); for (int posId = 0; posId < positions.size(); ++posId) { if (positions.at(posId)->axisRect() == this || positions.at(posId)->keyAxis()->axisRect() == this || positions.at(posId)->valueAxis()->axisRect() == this) { result.append(mParentPlot->mItems.at(itemId)); break; } } } return result; } /*! This method is called automatically upon replot and doesn't need to be called by users of QCPAxisRect. Calls the base class implementation to update the margins (see \ref QCPLayoutElement::update), and finally passes the \ref rect to the inset layout (\ref insetLayout) and calls its QCPInsetLayout::update function. \seebaseclassmethod */ void QCPAxisRect::update(UpdatePhase phase) { QCPLayoutElement::update(phase); switch (phase) { case upPreparation: { QList allAxes = axes(); for (int i = 0; i < allAxes.size(); ++i) allAxes.at(i)->setupTickVectors(); break; } case upLayout: { mInsetLayout->setOuterRect(rect()); break; } default: break; } // pass update call on to inset layout (doesn't happen automatically, because QCPAxisRect doesn't derive from QCPLayout): mInsetLayout->update(phase); } /* inherits documentation from base class */ QList QCPAxisRect::elements(bool recursive) const { QList result; if (mInsetLayout) { result << mInsetLayout; if (recursive) result << mInsetLayout->elements(recursive); } return result; } /* inherits documentation from base class */ void QCPAxisRect::applyDefaultAntialiasingHint(QCPPainter *painter) const { painter->setAntialiasing(false); } /* inherits documentation from base class */ void QCPAxisRect::draw(QCPPainter *painter) { drawBackground(painter); } /*! Sets \a pm as the axis background pixmap. The axis background pixmap will be drawn inside the axis rect. Since axis rects place themselves on the "background" layer by default, the axis rect backgrounds are usually drawn below everything else. For cases where the provided pixmap doesn't have the same size as the axis rect, scaling can be enabled with \ref setBackgroundScaled and the scaling mode (i.e. whether and how the aspect ratio is preserved) can be set with \ref setBackgroundScaledMode. To set all these options in one call, consider using the overloaded version of this function. Below the pixmap, the axis rect may be optionally filled with a brush, if specified with \ref setBackground(const QBrush &brush). \see setBackgroundScaled, setBackgroundScaledMode, setBackground(const QBrush &brush) */ void QCPAxisRect::setBackground(const QPixmap &pm) { mBackgroundPixmap = pm; mScaledBackgroundPixmap = QPixmap(); } /*! \overload Sets \a brush as the background brush. The axis rect background will be filled with this brush. Since axis rects place themselves on the "background" layer by default, the axis rect backgrounds are usually drawn below everything else. The brush will be drawn before (under) any background pixmap, which may be specified with \ref setBackground(const QPixmap &pm). To disable drawing of a background brush, set \a brush to Qt::NoBrush. \see setBackground(const QPixmap &pm) */ void QCPAxisRect::setBackground(const QBrush &brush) { mBackgroundBrush = brush; } /*! \overload Allows setting the background pixmap of the axis rect, whether it shall be scaled and how it shall be scaled in one call. \see setBackground(const QPixmap &pm), setBackgroundScaled, setBackgroundScaledMode */ void QCPAxisRect::setBackground(const QPixmap &pm, bool scaled, Qt::AspectRatioMode mode) { mBackgroundPixmap = pm; mScaledBackgroundPixmap = QPixmap(); mBackgroundScaled = scaled; mBackgroundScaledMode = mode; } /*! Sets whether the axis background pixmap shall be scaled to fit the axis rect or not. If \a scaled is set to true, you may control whether and how the aspect ratio of the original pixmap is preserved with \ref setBackgroundScaledMode. Note that the scaled version of the original pixmap is buffered, so there is no performance penalty on replots. (Except when the axis rect dimensions are changed continuously.) \see setBackground, setBackgroundScaledMode */ void QCPAxisRect::setBackgroundScaled(bool scaled) { mBackgroundScaled = scaled; } /*! If scaling of the axis background pixmap is enabled (\ref setBackgroundScaled), use this function to define whether and how the aspect ratio of the original pixmap passed to \ref setBackground is preserved. \see setBackground, setBackgroundScaled */ void QCPAxisRect::setBackgroundScaledMode(Qt::AspectRatioMode mode) { mBackgroundScaledMode = mode; } /*! Returns the range drag axis of the \a orientation provided. If multiple axes were set, returns the first one (use \ref rangeDragAxes to retrieve a list with all set axes). \see setRangeDragAxes */ QCPAxis *QCPAxisRect::rangeDragAxis(Qt::Orientation orientation) { if (orientation == Qt::Horizontal) - return mRangeDragHorzAxis.isEmpty() ? 0 : mRangeDragHorzAxis.first().data(); + return mRangeDragHorzAxis.isEmpty() ? nullptr : mRangeDragHorzAxis.first().data(); else - return mRangeDragVertAxis.isEmpty() ? 0 : mRangeDragVertAxis.first().data(); + return mRangeDragVertAxis.isEmpty() ? nullptr : mRangeDragVertAxis.first().data(); } /*! Returns the range zoom axis of the \a orientation provided. If multiple axes were set, returns the first one (use \ref rangeZoomAxes to retrieve a list with all set axes). \see setRangeZoomAxes */ QCPAxis *QCPAxisRect::rangeZoomAxis(Qt::Orientation orientation) { if (orientation == Qt::Horizontal) - return mRangeZoomHorzAxis.isEmpty() ? 0 : mRangeZoomHorzAxis.first().data(); + return mRangeZoomHorzAxis.isEmpty() ? nullptr : mRangeZoomHorzAxis.first().data(); else - return mRangeZoomVertAxis.isEmpty() ? 0 : mRangeZoomVertAxis.first().data(); + return mRangeZoomVertAxis.isEmpty() ? nullptr : mRangeZoomVertAxis.first().data(); } /*! Returns all range drag axes of the \a orientation provided. \see rangeZoomAxis, setRangeZoomAxes */ QList QCPAxisRect::rangeDragAxes(Qt::Orientation orientation) { QList result; if (orientation == Qt::Horizontal) { for (int i = 0; i < mRangeDragHorzAxis.size(); ++i) { if (!mRangeDragHorzAxis.at(i).isNull()) result.append(mRangeDragHorzAxis.at(i).data()); } } else { for (int i = 0; i < mRangeDragVertAxis.size(); ++i) { if (!mRangeDragVertAxis.at(i).isNull()) result.append(mRangeDragVertAxis.at(i).data()); } } return result; } /*! Returns all range zoom axes of the \a orientation provided. \see rangeDragAxis, setRangeDragAxes */ QList QCPAxisRect::rangeZoomAxes(Qt::Orientation orientation) { QList result; if (orientation == Qt::Horizontal) { for (int i = 0; i < mRangeZoomHorzAxis.size(); ++i) { if (!mRangeZoomHorzAxis.at(i).isNull()) result.append(mRangeZoomHorzAxis.at(i).data()); } } else { for (int i = 0; i < mRangeZoomVertAxis.size(); ++i) { if (!mRangeZoomVertAxis.at(i).isNull()) result.append(mRangeZoomVertAxis.at(i).data()); } } return result; } /*! Returns the range zoom factor of the \a orientation provided. \see setRangeZoomFactor */ double QCPAxisRect::rangeZoomFactor(Qt::Orientation orientation) { return (orientation == Qt::Horizontal ? mRangeZoomFactorHorz : mRangeZoomFactorVert); } /*! Sets which axis orientation may be range dragged by the user with mouse interaction. What orientation corresponds to which specific axis can be set with \ref setRangeDragAxes(QCPAxis *horizontal, QCPAxis *vertical). By default, the horizontal axis is the bottom axis (xAxis) and the vertical axis is the left axis (yAxis). To disable range dragging entirely, pass 0 as \a orientations or remove \ref QCP::iRangeDrag from \ref QCustomPlot::setInteractions. To enable range dragging for both directions, pass Qt::Horizontal | Qt::Vertical as \a orientations. In addition to setting \a orientations to a non-zero value, make sure \ref QCustomPlot::setInteractions contains \ref QCP::iRangeDrag to enable the range dragging interaction. \see setRangeZoom, setRangeDragAxes, QCustomPlot::setNoAntialiasingOnDrag */ void QCPAxisRect::setRangeDrag(Qt::Orientations orientations) { mRangeDrag = orientations; } /*! Sets which axis orientation may be zoomed by the user with the mouse wheel. What orientation corresponds to which specific axis can be set with \ref setRangeZoomAxes(QCPAxis *horizontal, QCPAxis *vertical). By default, the horizontal axis is the bottom axis (xAxis) and the vertical axis is the left axis (yAxis). To disable range zooming entirely, pass 0 as \a orientations or remove \ref QCP::iRangeZoom from \ref QCustomPlot::setInteractions. To enable range zooming for both directions, pass Qt::Horizontal | Qt::Vertical as \a orientations. In addition to setting \a orientations to a non-zero value, make sure \ref QCustomPlot::setInteractions contains \ref QCP::iRangeZoom to enable the range zooming interaction. \see setRangeZoomFactor, setRangeZoomAxes, setRangeDrag */ void QCPAxisRect::setRangeZoom(Qt::Orientations orientations) { mRangeZoom = orientations; } /*! \overload Sets the axes whose range will be dragged when \ref setRangeDrag enables mouse range dragging on the QCustomPlot widget. Pass 0 if no axis shall be dragged in the respective orientation. Use the overload taking a list of axes, if multiple axes (more than one per orientation) shall react to dragging interactions. \see setRangeZoomAxes */ void QCPAxisRect::setRangeDragAxes(QCPAxis *horizontal, QCPAxis *vertical) { QList horz, vert; if (horizontal) horz.append(horizontal); if (vertical) vert.append(vertical); setRangeDragAxes(horz, vert); } /*! \overload This method allows to set up multiple axes to react to horizontal and vertical dragging. The drag orientation that the respective axis will react to is deduced from its orientation (\ref QCPAxis::orientation). In the unusual case that you wish to e.g. drag a vertically oriented axis with a horizontal drag motion, use the overload taking two separate lists for horizontal and vertical dragging. */ void QCPAxisRect::setRangeDragAxes(QList axes) { QList horz, vert; foreach (QCPAxis *ax, axes) { if (ax->orientation() == Qt::Horizontal) horz.append(ax); else vert.append(ax); } setRangeDragAxes(horz, vert); } /*! \overload This method allows to set multiple axes up to react to horizontal and vertical dragging, and define specifically which axis reacts to which drag orientation (irrespective of the axis orientation). */ void QCPAxisRect::setRangeDragAxes(QList horizontal, QList vertical) { mRangeDragHorzAxis.clear(); foreach (QCPAxis *ax, horizontal) { QPointer axPointer(ax); if (!axPointer.isNull()) mRangeDragHorzAxis.append(axPointer); else qDebug() << Q_FUNC_INFO << "invalid axis passed in horizontal list:" << reinterpret_cast(ax); } mRangeDragVertAxis.clear(); foreach (QCPAxis *ax, vertical) { QPointer axPointer(ax); if (!axPointer.isNull()) mRangeDragVertAxis.append(axPointer); else qDebug() << Q_FUNC_INFO << "invalid axis passed in vertical list:" << reinterpret_cast(ax); } } /*! Sets the axes whose range will be zoomed when \ref setRangeZoom enables mouse wheel zooming on the QCustomPlot widget. Pass 0 if no axis shall be zoomed in the respective orientation. The two axes can be zoomed with different strengths, when different factors are passed to \ref setRangeZoomFactor(double horizontalFactor, double verticalFactor). Use the overload taking a list of axes, if multiple axes (more than one per orientation) shall react to zooming interactions. \see setRangeDragAxes */ void QCPAxisRect::setRangeZoomAxes(QCPAxis *horizontal, QCPAxis *vertical) { QList horz, vert; if (horizontal) horz.append(horizontal); if (vertical) vert.append(vertical); setRangeZoomAxes(horz, vert); } /*! \overload This method allows to set up multiple axes to react to horizontal and vertical range zooming. The zoom orientation that the respective axis will react to is deduced from its orientation (\ref QCPAxis::orientation). In the unusual case that you wish to e.g. zoom a vertically oriented axis with a horizontal zoom interaction, use the overload taking two separate lists for horizontal and vertical zooming. */ void QCPAxisRect::setRangeZoomAxes(QList axes) { QList horz, vert; foreach (QCPAxis *ax, axes) { if (ax->orientation() == Qt::Horizontal) horz.append(ax); else vert.append(ax); } setRangeZoomAxes(horz, vert); } /*! \overload This method allows to set multiple axes up to react to horizontal and vertical zooming, and define specifically which axis reacts to which zoom orientation (irrespective of the axis orientation). */ void QCPAxisRect::setRangeZoomAxes(QList horizontal, QList vertical) { mRangeZoomHorzAxis.clear(); foreach (QCPAxis *ax, horizontal) { QPointer axPointer(ax); if (!axPointer.isNull()) mRangeZoomHorzAxis.append(axPointer); else qDebug() << Q_FUNC_INFO << "invalid axis passed in horizontal list:" << reinterpret_cast(ax); } mRangeZoomVertAxis.clear(); foreach (QCPAxis *ax, vertical) { QPointer axPointer(ax); if (!axPointer.isNull()) mRangeZoomVertAxis.append(axPointer); else qDebug() << Q_FUNC_INFO << "invalid axis passed in vertical list:" << reinterpret_cast(ax); } } /*! Sets how strong one rotation step of the mouse wheel zooms, when range zoom was activated with \ref setRangeZoom. The two parameters \a horizontalFactor and \a verticalFactor provide a way to let the horizontal axis zoom at different rates than the vertical axis. Which axis is horizontal and which is vertical, can be set with \ref setRangeZoomAxes. When the zoom factor is greater than one, scrolling the mouse wheel backwards (towards the user) will zoom in (make the currently visible range smaller). For zoom factors smaller than one, the same scrolling direction will zoom out. */ void QCPAxisRect::setRangeZoomFactor(double horizontalFactor, double verticalFactor) { mRangeZoomFactorHorz = horizontalFactor; mRangeZoomFactorVert = verticalFactor; } /*! \overload Sets both the horizontal and vertical zoom \a factor. */ void QCPAxisRect::setRangeZoomFactor(double factor) { mRangeZoomFactorHorz = factor; mRangeZoomFactorVert = factor; } /*! \internal Draws the background of this axis rect. It may consist of a background fill (a QBrush) and a pixmap. If a brush was given via \ref setBackground(const QBrush &brush), this function first draws an according filling inside the axis rect with the provided \a painter. Then, if a pixmap was provided via \ref setBackground, this function buffers the scaled version depending on \ref setBackgroundScaled and \ref setBackgroundScaledMode and then draws it inside the axis rect with the provided \a painter. The scaled version is buffered in mScaledBackgroundPixmap to prevent expensive rescaling at every redraw. It is only updated, when the axis rect has changed in a way that requires a rescale of the background pixmap (this is dependent on the \ref setBackgroundScaledMode), or when a differend axis background pixmap was set. \see setBackground, setBackgroundScaled, setBackgroundScaledMode */ void QCPAxisRect::drawBackground(QCPPainter *painter) { // draw background fill: if (mBackgroundBrush != Qt::NoBrush) painter->fillRect(mRect, mBackgroundBrush); // draw background pixmap (on top of fill, if brush specified): if (!mBackgroundPixmap.isNull()) { if (mBackgroundScaled) { // check whether mScaledBackground needs to be updated: QSize scaledSize(mBackgroundPixmap.size()); scaledSize.scale(mRect.size(), mBackgroundScaledMode); if (mScaledBackgroundPixmap.size() != scaledSize) mScaledBackgroundPixmap = mBackgroundPixmap.scaled(mRect.size(), mBackgroundScaledMode, Qt::SmoothTransformation); painter->drawPixmap(mRect.topLeft() + QPoint(0, -1), mScaledBackgroundPixmap, QRect(0, 0, mRect.width(), mRect.height()) & mScaledBackgroundPixmap.rect()); } else { painter->drawPixmap(mRect.topLeft() + QPoint(0, -1), mBackgroundPixmap, QRect(0, 0, mRect.width(), mRect.height())); } } } /*! \internal This function makes sure multiple axes on the side specified with \a type don't collide, but are distributed according to their respective space requirement (QCPAxis::calculateMargin). It does this by setting an appropriate offset (\ref QCPAxis::setOffset) on all axes except the one with index zero. This function is called by \ref calculateAutoMargin. */ void QCPAxisRect::updateAxesOffset(QCPAxis::AxisType type) { const QList axesList = mAxes.value(type); if (axesList.isEmpty()) return; bool isFirstVisible = !axesList.first() ->visible(); // if the first axis is visible, the second axis (which is where the loop starts) isn't the first visible axis, so initialize with false for (int i = 1; i < axesList.size(); ++i) { int offset = axesList.at(i - 1)->offset() + axesList.at(i - 1)->calculateMargin(); if (axesList.at(i) ->visible()) // only add inner tick length to offset if this axis is visible and it's not the first visible one (might happen if true first axis is invisible) { if (!isFirstVisible) offset += axesList.at(i)->tickLengthIn(); isFirstVisible = false; } axesList.at(i)->setOffset(offset); } } /* inherits documentation from base class */ int QCPAxisRect::calculateAutoMargin(QCP::MarginSide side) { if (!mAutoMargins.testFlag(side)) qDebug() << Q_FUNC_INFO << "Called with side that isn't specified as auto margin"; updateAxesOffset(QCPAxis::marginSideToAxisType(side)); // note: only need to look at the last (outer most) axis to determine the total margin, due to updateAxisOffset call const QList axesList = mAxes.value(QCPAxis::marginSideToAxisType(side)); if (axesList.size() > 0) return axesList.last()->offset() + axesList.last()->calculateMargin(); else return 0; } /*! \internal Reacts to a change in layout to potentially set the convenience axis pointers \ref QCustomPlot::xAxis, \ref QCustomPlot::yAxis, etc. of the parent QCustomPlot to the respective axes of this axis rect. This is only done if the respective convenience pointer is currently zero and if there is no QCPAxisRect at position (0, 0) of the plot layout. This automation makes it simpler to replace the main axis rect with a newly created one, without the need to manually reset the convenience pointers. */ void QCPAxisRect::layoutChanged() { if (mParentPlot && mParentPlot->axisRectCount() > 0 && mParentPlot->axisRect(0) == this) { if (axisCount(QCPAxis::atBottom) > 0 && !mParentPlot->xAxis) mParentPlot->xAxis = axis(QCPAxis::atBottom); if (axisCount(QCPAxis::atLeft) > 0 && !mParentPlot->yAxis) mParentPlot->yAxis = axis(QCPAxis::atLeft); if (axisCount(QCPAxis::atTop) > 0 && !mParentPlot->xAxis2) mParentPlot->xAxis2 = axis(QCPAxis::atTop); if (axisCount(QCPAxis::atRight) > 0 && !mParentPlot->yAxis2) mParentPlot->yAxis2 = axis(QCPAxis::atRight); } } /*! \internal Event handler for when a mouse button is pressed on the axis rect. If the left mouse button is pressed, the range dragging interaction is initialized (the actual range manipulation happens in the \ref mouseMoveEvent). The mDragging flag is set to true and some anchor points are set that are needed to determine the distance the mouse was dragged in the mouse move/release events later. \see mouseMoveEvent, mouseReleaseEvent */ void QCPAxisRect::mousePressEvent(QMouseEvent *event, const QVariant &details) { Q_UNUSED(details) mDragStart = event ->pos(); // need this even when not LeftButton is pressed, to determine in releaseEvent whether it was a full click (no position change between press and release) if (event->buttons() & Qt::LeftButton) { mDragging = true; // initialize antialiasing backup in case we start dragging: if (mParentPlot->noAntialiasingOnDrag()) { mAADragBackup = mParentPlot->antialiasedElements(); mNotAADragBackup = mParentPlot->notAntialiasedElements(); } // Mouse range dragging interaction: if (mParentPlot->interactions().testFlag(QCP::iRangeDrag)) { mDragStartHorzRange.clear(); for (int i = 0; i < mRangeDragHorzAxis.size(); ++i) mDragStartHorzRange.append(mRangeDragHorzAxis.at(i).isNull() ? QCPRange() : mRangeDragHorzAxis.at(i)->range()); mDragStartVertRange.clear(); for (int i = 0; i < mRangeDragVertAxis.size(); ++i) mDragStartVertRange.append(mRangeDragVertAxis.at(i).isNull() ? QCPRange() : mRangeDragVertAxis.at(i)->range()); } } } /*! \internal Event handler for when the mouse is moved on the axis rect. If range dragging was activated in a preceding \ref mousePressEvent, the range is moved accordingly. \see mousePressEvent, mouseReleaseEvent */ void QCPAxisRect::mouseMoveEvent(QMouseEvent *event, const QPointF &startPos) { Q_UNUSED(startPos) // Mouse range dragging interaction: if (mDragging && mParentPlot->interactions().testFlag(QCP::iRangeDrag)) { if (mRangeDrag.testFlag(Qt::Horizontal)) { for (int i = 0; i < mRangeDragHorzAxis.size(); ++i) { QCPAxis *ax = mRangeDragHorzAxis.at(i).data(); if (!ax) continue; if (i >= mDragStartHorzRange.size()) break; if (ax->mScaleType == QCPAxis::stLinear) { double diff = ax->pixelToCoord(mDragStart.x()) - ax->pixelToCoord(event->pos().x()); ax->setRange(mDragStartHorzRange.at(i).lower + diff, mDragStartHorzRange.at(i).upper + diff); } else if (ax->mScaleType == QCPAxis::stLogarithmic) { double diff = ax->pixelToCoord(mDragStart.x()) / ax->pixelToCoord(event->pos().x()); ax->setRange(mDragStartHorzRange.at(i).lower * diff, mDragStartHorzRange.at(i).upper * diff); } } } if (mRangeDrag.testFlag(Qt::Vertical)) { for (int i = 0; i < mRangeDragVertAxis.size(); ++i) { QCPAxis *ax = mRangeDragVertAxis.at(i).data(); if (!ax) continue; if (i >= mDragStartVertRange.size()) break; if (ax->mScaleType == QCPAxis::stLinear) { double diff = ax->pixelToCoord(mDragStart.y()) - ax->pixelToCoord(event->pos().y()); ax->setRange(mDragStartVertRange.at(i).lower + diff, mDragStartVertRange.at(i).upper + diff); } else if (ax->mScaleType == QCPAxis::stLogarithmic) { double diff = ax->pixelToCoord(mDragStart.y()) / ax->pixelToCoord(event->pos().y()); ax->setRange(mDragStartVertRange.at(i).lower * diff, mDragStartVertRange.at(i).upper * diff); } } } if (mRangeDrag != 0) // if either vertical or horizontal drag was enabled, do a replot { if (mParentPlot->noAntialiasingOnDrag()) mParentPlot->setNotAntialiasedElements(QCP::aeAll); mParentPlot->replot(); } } } /* inherits documentation from base class */ void QCPAxisRect::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) { Q_UNUSED(event) Q_UNUSED(startPos) mDragging = false; if (mParentPlot->noAntialiasingOnDrag()) { mParentPlot->setAntialiasedElements(mAADragBackup); mParentPlot->setNotAntialiasedElements(mNotAADragBackup); } } /*! \internal Event handler for mouse wheel events. If rangeZoom is Qt::Horizontal, Qt::Vertical or both, the ranges of the axes defined as rangeZoomHorzAxis and rangeZoomVertAxis are scaled. The center of the scaling operation is the current cursor position inside the axis rect. The scaling factor is dependent on the mouse wheel delta (which direction the wheel was rotated) to provide a natural zooming feel. The Strength of the zoom can be controlled via \ref setRangeZoomFactor. Note, that event->delta() is usually +/-120 for single rotation steps. However, if the mouse wheel is turned rapidly, many steps may bunch up to one event, so the event->delta() may then be multiples of 120. This is taken into account here, by calculating \a wheelSteps and using it as exponent of the range zoom factor. This takes care of the wheel direction automatically, by inverting the factor, when the wheel step is negative (f^-1 = 1/f). */ void QCPAxisRect::wheelEvent(QWheelEvent *event) { // Mouse range zooming interaction: if (mParentPlot->interactions().testFlag(QCP::iRangeZoom)) { if (mRangeZoom != 0) { double factor; double wheelSteps = event->delta() / 120.0; // a single step delta is +/-120 usually if (mRangeZoom.testFlag(Qt::Horizontal)) { factor = qPow(mRangeZoomFactorHorz, wheelSteps); for (int i = 0; i < mRangeZoomHorzAxis.size(); ++i) { if (!mRangeZoomHorzAxis.at(i).isNull()) mRangeZoomHorzAxis.at(i)->scaleRange(factor, mRangeZoomHorzAxis.at(i)->pixelToCoord(event->pos().x())); } } if (mRangeZoom.testFlag(Qt::Vertical)) { factor = qPow(mRangeZoomFactorVert, wheelSteps); for (int i = 0; i < mRangeZoomVertAxis.size(); ++i) { if (!mRangeZoomVertAxis.at(i).isNull()) mRangeZoomVertAxis.at(i)->scaleRange(factor, mRangeZoomVertAxis.at(i)->pixelToCoord(event->pos().y())); } } mParentPlot->replot(); } } } /* end of 'src/layoutelements/layoutelement-axisrect.cpp' */ /* including file 'src/layoutelements/layoutelement-legend.cpp', size 30933 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAbstractLegendItem //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPAbstractLegendItem \brief The abstract base class for all entries in a QCPLegend. It defines a very basic interface for entries in a QCPLegend. For representing plottables in the legend, the subclass \ref QCPPlottableLegendItem is more suitable. Only derive directly from this class when you need absolute freedom (e.g. a custom legend entry that's not even associated with a plottable). You must implement the following pure virtual functions: \li \ref draw (from QCPLayerable) You inherit the following members you may use:
QCPLegend *\b mParentLegend A pointer to the parent QCPLegend.
QFont \b mFont The generic font of the item. You should use this font for all or at least the most prominent text of the item.
*/ /* start of documentation of signals */ /*! \fn void QCPAbstractLegendItem::selectionChanged(bool selected) This signal is emitted when the selection state of this legend item has changed, either by user interaction or by a direct call to \ref setSelected. */ /* end of documentation of signals */ /*! Constructs a QCPAbstractLegendItem and associates it with the QCPLegend \a parent. This does not cause the item to be added to \a parent, so \ref QCPLegend::addItem must be called separately. */ QCPAbstractLegendItem::QCPAbstractLegendItem(QCPLegend *parent) : QCPLayoutElement(parent->parentPlot()), mParentLegend(parent), mFont(parent->font()), mTextColor(parent->textColor()), mSelectedFont(parent->selectedFont()), mSelectedTextColor(parent->selectedTextColor()), mSelectable(true), mSelected(false) { setLayer(QLatin1String("legend")); setMargins(QMargins(0, 0, 0, 0)); } /*! Sets the default font of this specific legend item to \a font. \see setTextColor, QCPLegend::setFont */ void QCPAbstractLegendItem::setFont(const QFont &font) { mFont = font; } /*! Sets the default text color of this specific legend item to \a color. \see setFont, QCPLegend::setTextColor */ void QCPAbstractLegendItem::setTextColor(const QColor &color) { mTextColor = color; } /*! When this legend item is selected, \a font is used to draw generic text, instead of the normal font set with \ref setFont. \see setFont, QCPLegend::setSelectedFont */ void QCPAbstractLegendItem::setSelectedFont(const QFont &font) { mSelectedFont = font; } /*! When this legend item is selected, \a color is used to draw generic text, instead of the normal color set with \ref setTextColor. \see setTextColor, QCPLegend::setSelectedTextColor */ void QCPAbstractLegendItem::setSelectedTextColor(const QColor &color) { mSelectedTextColor = color; } /*! Sets whether this specific legend item is selectable. \see setSelectedParts, QCustomPlot::setInteractions */ void QCPAbstractLegendItem::setSelectable(bool selectable) { if (mSelectable != selectable) { mSelectable = selectable; emit selectableChanged(mSelectable); } } /*! Sets whether this specific legend item is selected. It is possible to set the selection state of this item by calling this function directly, even if setSelectable is set to false. \see setSelectableParts, QCustomPlot::setInteractions */ void QCPAbstractLegendItem::setSelected(bool selected) { if (mSelected != selected) { mSelected = selected; emit selectionChanged(mSelected); } } /* inherits documentation from base class */ double QCPAbstractLegendItem::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if (!mParentPlot) return -1; if (onlySelectable && (!mSelectable || !mParentLegend->selectableParts().testFlag(QCPLegend::spItems))) return -1; if (mRect.contains(pos.toPoint())) return mParentPlot->selectionTolerance() * 0.99; else return -1; } /* inherits documentation from base class */ void QCPAbstractLegendItem::applyDefaultAntialiasingHint(QCPPainter *painter) const { applyAntialiasingHint(painter, mAntialiased, QCP::aeLegendItems); } /* inherits documentation from base class */ QRect QCPAbstractLegendItem::clipRect() const { return mOuterRect; } /* inherits documentation from base class */ void QCPAbstractLegendItem::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) { Q_UNUSED(event) Q_UNUSED(details) if (mSelectable && mParentLegend->selectableParts().testFlag(QCPLegend::spItems)) { bool selBefore = mSelected; setSelected(additive ? !mSelected : true); if (selectionStateChanged) *selectionStateChanged = mSelected != selBefore; } } /* inherits documentation from base class */ void QCPAbstractLegendItem::deselectEvent(bool *selectionStateChanged) { if (mSelectable && mParentLegend->selectableParts().testFlag(QCPLegend::spItems)) { bool selBefore = mSelected; setSelected(false); if (selectionStateChanged) *selectionStateChanged = mSelected != selBefore; } } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPPlottableLegendItem //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPPlottableLegendItem \brief A legend item representing a plottable with an icon and the plottable name. This is the standard legend item for plottables. It displays an icon of the plottable next to the plottable name. The icon is drawn by the respective plottable itself (\ref QCPAbstractPlottable::drawLegendIcon), and tries to give an intuitive symbol for the plottable. For example, the QCPGraph draws a centered horizontal line and/or a single scatter point in the middle. Legend items of this type are always associated with one plottable (retrievable via the plottable() function and settable with the constructor). You may change the font of the plottable name with \ref setFont. Icon padding and border pen is taken from the parent QCPLegend, see \ref QCPLegend::setIconBorderPen and \ref QCPLegend::setIconTextPadding. The function \ref QCPAbstractPlottable::addToLegend/\ref QCPAbstractPlottable::removeFromLegend creates/removes legend items of this type in the default implementation. However, these functions may be reimplemented such that a different kind of legend item (e.g a direct subclass of QCPAbstractLegendItem) is used for that plottable. Since QCPLegend is based on QCPLayoutGrid, a legend item itself is just a subclass of QCPLayoutElement. While it could be added to a legend (or any other layout) via the normal layout interface, QCPLegend has specialized functions for handling legend items conveniently, see the documentation of \ref QCPLegend. */ /*! Creates a new legend item associated with \a plottable. Once it's created, it can be added to the legend via \ref QCPLegend::addItem. A more convenient way of adding/removing a plottable to/from the legend is via the functions \ref QCPAbstractPlottable::addToLegend and \ref QCPAbstractPlottable::removeFromLegend. */ QCPPlottableLegendItem::QCPPlottableLegendItem(QCPLegend *parent, QCPAbstractPlottable *plottable) : QCPAbstractLegendItem(parent), mPlottable(plottable) { setAntialiased(false); } /*! \internal Returns the pen that shall be used to draw the icon border, taking into account the selection state of this item. */ QPen QCPPlottableLegendItem::getIconBorderPen() const { return mSelected ? mParentLegend->selectedIconBorderPen() : mParentLegend->iconBorderPen(); } /*! \internal Returns the text color that shall be used to draw text, taking into account the selection state of this item. */ QColor QCPPlottableLegendItem::getTextColor() const { return mSelected ? mSelectedTextColor : mTextColor; } /*! \internal Returns the font that shall be used to draw text, taking into account the selection state of this item. */ QFont QCPPlottableLegendItem::getFont() const { return mSelected ? mSelectedFont : mFont; } /*! \internal Draws the item with \a painter. The size and position of the drawn legend item is defined by the parent layout (typically a \ref QCPLegend) and the \ref minimumSizeHint and \ref maximumSizeHint of this legend item. */ void QCPPlottableLegendItem::draw(QCPPainter *painter) { if (!mPlottable) return; painter->setFont(getFont()); painter->setPen(QPen(getTextColor())); QSizeF iconSize = mParentLegend->iconSize(); QRectF textRect = painter->fontMetrics().boundingRect(0, 0, 0, iconSize.height(), Qt::TextDontClip, mPlottable->name()); QRectF iconRect(mRect.topLeft(), iconSize); int textHeight = qMax( textRect.height(), iconSize .height()); // if text has smaller height than icon, center text vertically in icon height, else align tops painter->drawText(mRect.x() + iconSize.width() + mParentLegend->iconTextPadding(), mRect.y(), textRect.width(), textHeight, Qt::TextDontClip, mPlottable->name()); // draw icon: painter->save(); painter->setClipRect(iconRect, Qt::IntersectClip); mPlottable->drawLegendIcon(painter, iconRect); painter->restore(); // draw icon border: if (getIconBorderPen().style() != Qt::NoPen) { painter->setPen(getIconBorderPen()); painter->setBrush(Qt::NoBrush); int halfPen = qCeil(painter->pen().widthF() * 0.5) + 1; painter->setClipRect(mOuterRect.adjusted( -halfPen, -halfPen, halfPen, halfPen)); // extend default clip rect so thicker pens (especially during selection) are not clipped painter->drawRect(iconRect); } } /*! \internal Calculates and returns the size of this item. This includes the icon, the text and the padding in between. \seebaseclassmethod */ QSize QCPPlottableLegendItem::minimumSizeHint() const { if (!mPlottable) return QSize(); QSize result(0, 0); QRect textRect; QFontMetrics fontMetrics(getFont()); QSize iconSize = mParentLegend->iconSize(); textRect = fontMetrics.boundingRect(0, 0, 0, iconSize.height(), Qt::TextDontClip, mPlottable->name()); result.setWidth(iconSize.width() + mParentLegend->iconTextPadding() + textRect.width() + mMargins.left() + mMargins.right()); result.setHeight(qMax(textRect.height(), iconSize.height()) + mMargins.top() + mMargins.bottom()); return result; } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPLegend //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPLegend \brief Manages a legend inside a QCustomPlot. A legend is a small box somewhere in the plot which lists plottables with their name and icon. Normally, the legend is populated by calling \ref QCPAbstractPlottable::addToLegend. The respective legend item can be removed with \ref QCPAbstractPlottable::removeFromLegend. However, QCPLegend also offers an interface to add and manipulate legend items directly: \ref item, \ref itemWithPlottable, \ref itemCount, \ref addItem, \ref removeItem, etc. Since \ref QCPLegend derives from \ref QCPLayoutGrid, it can be placed in any position a \ref QCPLayoutElement may be positioned. The legend items are themselves \ref QCPLayoutElement "QCPLayoutElements" which are placed in the grid layout of the legend. \ref QCPLegend only adds an interface specialized for handling child elements of type \ref QCPAbstractLegendItem, as mentioned above. In principle, any other layout elements may also be added to a legend via the normal \ref QCPLayoutGrid interface. See the special page about \link thelayoutsystem The Layout System\endlink for examples on how to add other elements to the legend and move it outside the axis rect. Use the methods \ref setFillOrder and \ref setWrap inherited from \ref QCPLayoutGrid to control in which order (column first or row first) the legend is filled up when calling \ref addItem, and at which column or row wrapping occurs. By default, every QCustomPlot has one legend (\ref QCustomPlot::legend) which is placed in the inset layout of the main axis rect (\ref QCPAxisRect::insetLayout). To move the legend to another position inside the axis rect, use the methods of the \ref QCPLayoutInset. To move the legend outside of the axis rect, place it anywhere else with the \ref QCPLayout/\ref QCPLayoutElement interface. */ /* start of documentation of signals */ /*! \fn void QCPLegend::selectionChanged(QCPLegend::SelectableParts selection); This signal is emitted when the selection state of this legend has changed. \see setSelectedParts, setSelectableParts */ /* end of documentation of signals */ /*! Constructs a new QCPLegend instance with default values. Note that by default, QCustomPlot already contains a legend ready to be used as \ref QCustomPlot::legend */ QCPLegend::QCPLegend() { setFillOrder(QCPLayoutGrid::foRowsFirst); setWrap(0); setRowSpacing(3); setColumnSpacing(8); setMargins(QMargins(7, 5, 7, 4)); setAntialiased(false); setIconSize(32, 18); setIconTextPadding(7); setSelectableParts(spLegendBox | spItems); setSelectedParts(spNone); setBorderPen(QPen(Qt::black, 0)); setSelectedBorderPen(QPen(Qt::blue, 2)); setIconBorderPen(Qt::NoPen); setSelectedIconBorderPen(QPen(Qt::blue, 2)); setBrush(Qt::white); setSelectedBrush(Qt::white); setTextColor(Qt::black); setSelectedTextColor(Qt::blue); } QCPLegend::~QCPLegend() { clearItems(); if (qobject_cast( mParentPlot)) // make sure this isn't called from QObject dtor when QCustomPlot is already destructed (happens when the legend is not in any layout and thus QObject-child of QCustomPlot) mParentPlot->legendRemoved(this); } /* no doc for getter, see setSelectedParts */ QCPLegend::SelectableParts QCPLegend::selectedParts() const { // check whether any legend elements selected, if yes, add spItems to return value bool hasSelectedItems = false; for (int i = 0; i < itemCount(); ++i) { if (item(i) && item(i)->selected()) { hasSelectedItems = true; break; } } if (hasSelectedItems) return mSelectedParts | spItems; else return mSelectedParts & ~spItems; } /*! Sets the pen, the border of the entire legend is drawn with. */ void QCPLegend::setBorderPen(const QPen &pen) { mBorderPen = pen; } /*! Sets the brush of the legend background. */ void QCPLegend::setBrush(const QBrush &brush) { mBrush = brush; } /*! Sets the default font of legend text. Legend items that draw text (e.g. the name of a graph) will use this font by default. However, a different font can be specified on a per-item-basis by accessing the specific legend item. This function will also set \a font on all already existing legend items. \see QCPAbstractLegendItem::setFont */ void QCPLegend::setFont(const QFont &font) { mFont = font; for (int i = 0; i < itemCount(); ++i) { if (item(i)) item(i)->setFont(mFont); } } /*! Sets the default color of legend text. Legend items that draw text (e.g. the name of a graph) will use this color by default. However, a different colors can be specified on a per-item-basis by accessing the specific legend item. This function will also set \a color on all already existing legend items. \see QCPAbstractLegendItem::setTextColor */ void QCPLegend::setTextColor(const QColor &color) { mTextColor = color; for (int i = 0; i < itemCount(); ++i) { if (item(i)) item(i)->setTextColor(color); } } /*! Sets the size of legend icons. Legend items that draw an icon (e.g. a visual representation of the graph) will use this size by default. */ void QCPLegend::setIconSize(const QSize &size) { mIconSize = size; } /*! \overload */ void QCPLegend::setIconSize(int width, int height) { mIconSize.setWidth(width); mIconSize.setHeight(height); } /*! Sets the horizontal space in pixels between the legend icon and the text next to it. Legend items that draw an icon (e.g. a visual representation of the graph) and text (e.g. the name of the graph) will use this space by default. */ void QCPLegend::setIconTextPadding(int padding) { mIconTextPadding = padding; } /*! Sets the pen used to draw a border around each legend icon. Legend items that draw an icon (e.g. a visual representation of the graph) will use this pen by default. If no border is wanted, set this to \a Qt::NoPen. */ void QCPLegend::setIconBorderPen(const QPen &pen) { mIconBorderPen = pen; } /*! Sets whether the user can (de-)select the parts in \a selectable by clicking on the QCustomPlot surface. (When \ref QCustomPlot::setInteractions contains \ref QCP::iSelectLegend.) However, even when \a selectable is set to a value not allowing the selection of a specific part, it is still possible to set the selection of this part manually, by calling \ref setSelectedParts directly. \see SelectablePart, setSelectedParts */ void QCPLegend::setSelectableParts(const SelectableParts &selectable) { if (mSelectableParts != selectable) { mSelectableParts = selectable; emit selectableChanged(mSelectableParts); } } /*! Sets the selected state of the respective legend parts described by \ref SelectablePart. When a part is selected, it uses a different pen/font and brush. If some legend items are selected and \a selected doesn't contain \ref spItems, those items become deselected. The entire selection mechanism is handled automatically when \ref QCustomPlot::setInteractions contains iSelectLegend. You only need to call this function when you wish to change the selection state manually. This function can change the selection state of a part even when \ref setSelectableParts was set to a value that actually excludes the part. emits the \ref selectionChanged signal when \a selected is different from the previous selection state. Note that it doesn't make sense to set the selected state \ref spItems here when it wasn't set before, because there's no way to specify which exact items to newly select. Do this by calling \ref QCPAbstractLegendItem::setSelected directly on the legend item you wish to select. \see SelectablePart, setSelectableParts, selectTest, setSelectedBorderPen, setSelectedIconBorderPen, setSelectedBrush, setSelectedFont */ void QCPLegend::setSelectedParts(const SelectableParts &selected) { SelectableParts newSelected = selected; mSelectedParts = this->selectedParts(); // update mSelectedParts in case item selection changed if (mSelectedParts != newSelected) { if (!mSelectedParts.testFlag(spItems) && newSelected.testFlag(spItems)) // attempt to set spItems flag (can't do that) { qDebug() << Q_FUNC_INFO << "spItems flag can not be set, it can only be unset with this function"; newSelected &= ~spItems; } if (mSelectedParts.testFlag(spItems) && !newSelected.testFlag(spItems)) // spItems flag was unset, so clear item selection { for (int i = 0; i < itemCount(); ++i) { if (item(i)) item(i)->setSelected(false); } } mSelectedParts = newSelected; emit selectionChanged(mSelectedParts); } } /*! When the legend box is selected, this pen is used to draw the border instead of the normal pen set via \ref setBorderPen. \see setSelectedParts, setSelectableParts, setSelectedBrush */ void QCPLegend::setSelectedBorderPen(const QPen &pen) { mSelectedBorderPen = pen; } /*! Sets the pen legend items will use to draw their icon borders, when they are selected. \see setSelectedParts, setSelectableParts, setSelectedFont */ void QCPLegend::setSelectedIconBorderPen(const QPen &pen) { mSelectedIconBorderPen = pen; } /*! When the legend box is selected, this brush is used to draw the legend background instead of the normal brush set via \ref setBrush. \see setSelectedParts, setSelectableParts, setSelectedBorderPen */ void QCPLegend::setSelectedBrush(const QBrush &brush) { mSelectedBrush = brush; } /*! Sets the default font that is used by legend items when they are selected. This function will also set \a font on all already existing legend items. \see setFont, QCPAbstractLegendItem::setSelectedFont */ void QCPLegend::setSelectedFont(const QFont &font) { mSelectedFont = font; for (int i = 0; i < itemCount(); ++i) { if (item(i)) item(i)->setSelectedFont(font); } } /*! Sets the default text color that is used by legend items when they are selected. This function will also set \a color on all already existing legend items. \see setTextColor, QCPAbstractLegendItem::setSelectedTextColor */ void QCPLegend::setSelectedTextColor(const QColor &color) { mSelectedTextColor = color; for (int i = 0; i < itemCount(); ++i) { if (item(i)) item(i)->setSelectedTextColor(color); } } /*! Returns the item with index \a i. Note that the linear index depends on the current fill order (\ref setFillOrder). \see itemCount, addItem, itemWithPlottable */ QCPAbstractLegendItem *QCPLegend::item(int index) const { return qobject_cast(elementAt(index)); } /*! Returns the QCPPlottableLegendItem which is associated with \a plottable (e.g. a \ref QCPGraph*). If such an item isn't in the legend, returns 0. \see hasItemWithPlottable */ QCPPlottableLegendItem *QCPLegend::itemWithPlottable(const QCPAbstractPlottable *plottable) const { for (int i = 0; i < itemCount(); ++i) { if (QCPPlottableLegendItem *pli = qobject_cast(item(i))) { if (pli->plottable() == plottable) return pli; } } - return 0; + return nullptr; } /*! Returns the number of items currently in the legend. Note that if empty cells are in the legend (e.g. by calling methods of the \ref QCPLayoutGrid base class which allows creating empty cells), they are included in the returned count. \see item */ int QCPLegend::itemCount() const { return elementCount(); } /*! Returns whether the legend contains \a item. \see hasItemWithPlottable */ bool QCPLegend::hasItem(QCPAbstractLegendItem *item) const { for (int i = 0; i < itemCount(); ++i) { if (item == this->item(i)) return true; } return false; } /*! Returns whether the legend contains a QCPPlottableLegendItem which is associated with \a plottable (e.g. a \ref QCPGraph*). If such an item isn't in the legend, returns false. \see itemWithPlottable */ bool QCPLegend::hasItemWithPlottable(const QCPAbstractPlottable *plottable) const { return itemWithPlottable(plottable); } /*! Adds \a item to the legend, if it's not present already. The element is arranged according to the current fill order (\ref setFillOrder) and wrapping (\ref setWrap). Returns true on sucess, i.e. if the item wasn't in the list already and has been successfuly added. The legend takes ownership of the item. \see removeItem, item, hasItem */ bool QCPLegend::addItem(QCPAbstractLegendItem *item) { return addElement(item); } /*! \overload Removes the item with the specified \a index from the legend and deletes it. After successful removal, the legend is reordered according to the current fill order (\ref setFillOrder) and wrapping (\ref setWrap), so no empty cell remains where the removed \a item was. If you don't want this, rather use the raw element interface of \ref QCPLayoutGrid. Returns true, if successful. Unlike \ref QCPLayoutGrid::removeAt, this method only removes elements derived from \ref QCPAbstractLegendItem. \see itemCount, clearItems */ bool QCPLegend::removeItem(int index) { if (QCPAbstractLegendItem *ali = item(index)) { bool success = remove(ali); if (success) setFillOrder(fillOrder(), true); // gets rid of empty cell by reordering return success; } else return false; } /*! \overload Removes \a item from the legend and deletes it. After successful removal, the legend is reordered according to the current fill order (\ref setFillOrder) and wrapping (\ref setWrap), so no empty cell remains where the removed \a item was. If you don't want this, rather use the raw element interface of \ref QCPLayoutGrid. Returns true, if successful. \see clearItems */ bool QCPLegend::removeItem(QCPAbstractLegendItem *item) { bool success = remove(item); if (success) setFillOrder(fillOrder(), true); // gets rid of empty cell by reordering return success; } /*! Removes all items from the legend. */ void QCPLegend::clearItems() { for (int i = itemCount() - 1; i >= 0; --i) removeItem(i); } /*! Returns the legend items that are currently selected. If no items are selected, the list is empty. \see QCPAbstractLegendItem::setSelected, setSelectable */ QList QCPLegend::selectedItems() const { QList result; for (int i = 0; i < itemCount(); ++i) { if (QCPAbstractLegendItem *ali = item(i)) { if (ali->selected()) result.append(ali); } } return result; } /*! \internal A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter before drawing main legend elements. This is the antialiasing state the painter passed to the \ref draw method is in by default. This function takes into account the local setting of the antialiasing flag as well as the overrides set with \ref QCustomPlot::setAntialiasedElements and \ref QCustomPlot::setNotAntialiasedElements. \seebaseclassmethod \see setAntialiased */ void QCPLegend::applyDefaultAntialiasingHint(QCPPainter *painter) const { applyAntialiasingHint(painter, mAntialiased, QCP::aeLegend); } /*! \internal Returns the pen used to paint the border of the legend, taking into account the selection state of the legend box. */ QPen QCPLegend::getBorderPen() const { return mSelectedParts.testFlag(spLegendBox) ? mSelectedBorderPen : mBorderPen; } /*! \internal Returns the brush used to paint the background of the legend, taking into account the selection state of the legend box. */ QBrush QCPLegend::getBrush() const { return mSelectedParts.testFlag(spLegendBox) ? mSelectedBrush : mBrush; } /*! \internal Draws the legend box with the provided \a painter. The individual legend items are layerables themselves, thus are drawn independently. */ void QCPLegend::draw(QCPPainter *painter) { // draw background rect: painter->setBrush(getBrush()); painter->setPen(getBorderPen()); painter->drawRect(mOuterRect); } /* inherits documentation from base class */ double QCPLegend::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { if (!mParentPlot) return -1; if (onlySelectable && !mSelectableParts.testFlag(spLegendBox)) return -1; if (mOuterRect.contains(pos.toPoint())) { if (details) details->setValue(spLegendBox); return mParentPlot->selectionTolerance() * 0.99; } return -1; } /* inherits documentation from base class */ void QCPLegend::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) { Q_UNUSED(event) mSelectedParts = selectedParts(); // in case item selection has changed if (details.value() == spLegendBox && mSelectableParts.testFlag(spLegendBox)) { SelectableParts selBefore = mSelectedParts; setSelectedParts( additive ? mSelectedParts ^ spLegendBox : mSelectedParts | spLegendBox); // no need to unset spItems in !additive case, because they will be deselected by QCustomPlot (they're normal QCPLayerables with own deselectEvent) if (selectionStateChanged) *selectionStateChanged = mSelectedParts != selBefore; } } /* inherits documentation from base class */ void QCPLegend::deselectEvent(bool *selectionStateChanged) { mSelectedParts = selectedParts(); // in case item selection has changed if (mSelectableParts.testFlag(spLegendBox)) { SelectableParts selBefore = mSelectedParts; setSelectedParts(selectedParts() & ~spLegendBox); if (selectionStateChanged) *selectionStateChanged = mSelectedParts != selBefore; } } /* inherits documentation from base class */ QCP::Interaction QCPLegend::selectionCategory() const { return QCP::iSelectLegend; } /* inherits documentation from base class */ QCP::Interaction QCPAbstractLegendItem::selectionCategory() const { return QCP::iSelectLegend; } /* inherits documentation from base class */ void QCPLegend::parentPlotInitialized(QCustomPlot *parentPlot) { if (parentPlot && !parentPlot->legend) parentPlot->legend = this; } /* end of 'src/layoutelements/layoutelement-legend.cpp' */ /* including file 'src/layoutelements/layoutelement-textelement.cpp', size 12759 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPTextElement //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPTextElement \brief A layout element displaying a text The text may be specified with \ref setText, the formatting can be controlled with \ref setFont, \ref setTextColor, and \ref setTextFlags. A text element can be added as follows: \snippet documentation/doc-code-snippets/mainwindow.cpp qcptextelement-creation */ /* start documentation of signals */ /*! \fn void QCPTextElement::selectionChanged(bool selected) This signal is emitted when the selection state has changed to \a selected, either by user interaction or by a direct call to \ref setSelected. \see setSelected, setSelectable */ /*! \fn void QCPTextElement::clicked(QMouseEvent *event) This signal is emitted when the text element is clicked. \see doubleClicked, selectTest */ /*! \fn void QCPTextElement::doubleClicked(QMouseEvent *event) This signal is emitted when the text element is double clicked. \see clicked, selectTest */ /* end documentation of signals */ /*! \overload Creates a new QCPTextElement instance and sets default values. The initial text is empty (\ref setText). */ QCPTextElement::QCPTextElement(QCustomPlot *parentPlot) : QCPLayoutElement(parentPlot), mText(), mTextFlags(Qt::AlignCenter | Qt::TextWordWrap), mFont(QFont(QLatin1String("sans serif"), 12)), // will be taken from parentPlot if available, see below mTextColor(Qt::black), mSelectedFont(QFont(QLatin1String("sans serif"), 12)), // will be taken from parentPlot if available, see below mSelectedTextColor(Qt::blue), mSelectable(false), mSelected(false) { if (parentPlot) { mFont = parentPlot->font(); mSelectedFont = parentPlot->font(); } setMargins(QMargins(2, 2, 2, 2)); } /*! \overload Creates a new QCPTextElement instance and sets default values. The initial text is set to \a text. */ QCPTextElement::QCPTextElement(QCustomPlot *parentPlot, const QString &text) : QCPLayoutElement(parentPlot), mText(text), mTextFlags(Qt::AlignCenter | Qt::TextWordWrap), mFont(QFont(QLatin1String("sans serif"), 12)), // will be taken from parentPlot if available, see below mTextColor(Qt::black), mSelectedFont(QFont(QLatin1String("sans serif"), 12)), // will be taken from parentPlot if available, see below mSelectedTextColor(Qt::blue), mSelectable(false), mSelected(false) { if (parentPlot) { mFont = parentPlot->font(); mSelectedFont = parentPlot->font(); } setMargins(QMargins(2, 2, 2, 2)); } /*! \overload Creates a new QCPTextElement instance and sets default values. The initial text is set to \a text with \a pointSize. */ QCPTextElement::QCPTextElement(QCustomPlot *parentPlot, const QString &text, double pointSize) : QCPLayoutElement(parentPlot), mText(text), mTextFlags(Qt::AlignCenter | Qt::TextWordWrap), mFont(QFont(QLatin1String("sans serif"), pointSize)), // will be taken from parentPlot if available, see below mTextColor(Qt::black), mSelectedFont(QFont(QLatin1String("sans serif"), pointSize)), // will be taken from parentPlot if available, see below mSelectedTextColor(Qt::blue), mSelectable(false), mSelected(false) { if (parentPlot) { mFont = parentPlot->font(); mFont.setPointSizeF(pointSize); mSelectedFont = parentPlot->font(); mSelectedFont.setPointSizeF(pointSize); } setMargins(QMargins(2, 2, 2, 2)); } /*! \overload Creates a new QCPTextElement instance and sets default values. The initial text is set to \a text with \a pointSize and the specified \a fontFamily. */ QCPTextElement::QCPTextElement(QCustomPlot *parentPlot, const QString &text, const QString &fontFamily, double pointSize) : QCPLayoutElement(parentPlot), mText(text), mTextFlags(Qt::AlignCenter | Qt::TextWordWrap), mFont(QFont(fontFamily, pointSize)), mTextColor(Qt::black), mSelectedFont(QFont(fontFamily, pointSize)), mSelectedTextColor(Qt::blue), mSelectable(false), mSelected(false) { setMargins(QMargins(2, 2, 2, 2)); } /*! \overload Creates a new QCPTextElement instance and sets default values. The initial text is set to \a text with the specified \a font. */ QCPTextElement::QCPTextElement(QCustomPlot *parentPlot, const QString &text, const QFont &font) : QCPLayoutElement(parentPlot), mText(text), mTextFlags(Qt::AlignCenter | Qt::TextWordWrap), mFont(font), mTextColor(Qt::black), mSelectedFont(font), mSelectedTextColor(Qt::blue), mSelectable(false), mSelected(false) { setMargins(QMargins(2, 2, 2, 2)); } /*! Sets the text that will be displayed to \a text. Multiple lines can be created by insertion of "\n". \see setFont, setTextColor, setTextFlags */ void QCPTextElement::setText(const QString &text) { mText = text; } /*! Sets options for text alignment and wrapping behaviour. \a flags is a bitwise OR-combination of \c Qt::AlignmentFlag and \c Qt::TextFlag enums. Possible enums are: - Qt::AlignLeft - Qt::AlignRight - Qt::AlignHCenter - Qt::AlignJustify - Qt::AlignTop - Qt::AlignBottom - Qt::AlignVCenter - Qt::AlignCenter - Qt::TextDontClip - Qt::TextSingleLine - Qt::TextExpandTabs - Qt::TextShowMnemonic - Qt::TextWordWrap - Qt::TextIncludeTrailingSpaces */ void QCPTextElement::setTextFlags(int flags) { mTextFlags = flags; } /*! Sets the \a font of the text. \see setTextColor, setSelectedFont */ void QCPTextElement::setFont(const QFont &font) { mFont = font; } /*! Sets the \a color of the text. \see setFont, setSelectedTextColor */ void QCPTextElement::setTextColor(const QColor &color) { mTextColor = color; } /*! Sets the \a font of the text that will be used if the text element is selected (\ref setSelected). \see setFont */ void QCPTextElement::setSelectedFont(const QFont &font) { mSelectedFont = font; } /*! Sets the \a color of the text that will be used if the text element is selected (\ref setSelected). \see setTextColor */ void QCPTextElement::setSelectedTextColor(const QColor &color) { mSelectedTextColor = color; } /*! Sets whether the user may select this text element. Note that even when \a selectable is set to false, the selection state may be changed programmatically via \ref setSelected. */ void QCPTextElement::setSelectable(bool selectable) { if (mSelectable != selectable) { mSelectable = selectable; emit selectableChanged(mSelectable); } } /*! Sets the selection state of this text element to \a selected. If the selection has changed, \ref selectionChanged is emitted. Note that this function can change the selection state independently of the current \ref setSelectable state. */ void QCPTextElement::setSelected(bool selected) { if (mSelected != selected) { mSelected = selected; emit selectionChanged(mSelected); } } /* inherits documentation from base class */ void QCPTextElement::applyDefaultAntialiasingHint(QCPPainter *painter) const { applyAntialiasingHint(painter, mAntialiased, QCP::aeOther); } /* inherits documentation from base class */ void QCPTextElement::draw(QCPPainter *painter) { painter->setFont(mainFont()); painter->setPen(QPen(mainTextColor())); painter->drawText(mRect, Qt::AlignCenter, mText, &mTextBoundingRect); } /* inherits documentation from base class */ QSize QCPTextElement::minimumSizeHint() const { QFontMetrics metrics(mFont); QSize result = metrics.boundingRect(0, 0, 0, 0, Qt::AlignCenter, mText).size(); result.rwidth() += mMargins.left() + mMargins.right(); result.rheight() += mMargins.top() + mMargins.bottom(); return result; } /* inherits documentation from base class */ QSize QCPTextElement::maximumSizeHint() const { QFontMetrics metrics(mFont); QSize result = metrics.boundingRect(0, 0, 0, 0, Qt::AlignCenter, mText).size(); result.rheight() += mMargins.top() + mMargins.bottom(); result.setWidth(QWIDGETSIZE_MAX); return result; } /* inherits documentation from base class */ void QCPTextElement::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) { Q_UNUSED(event) Q_UNUSED(details) if (mSelectable) { bool selBefore = mSelected; setSelected(additive ? !mSelected : true); if (selectionStateChanged) *selectionStateChanged = mSelected != selBefore; } } /* inherits documentation from base class */ void QCPTextElement::deselectEvent(bool *selectionStateChanged) { if (mSelectable) { bool selBefore = mSelected; setSelected(false); if (selectionStateChanged) *selectionStateChanged = mSelected != selBefore; } } /*! Returns 0.99*selectionTolerance (see \ref QCustomPlot::setSelectionTolerance) when \a pos is within the bounding box of the text element's text. Note that this bounding box is updated in the draw call. If \a pos is outside the text's bounding box or if \a onlySelectable is true and this text element is not selectable (\ref setSelectable), returns -1. \seebaseclassmethod */ double QCPTextElement::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if (onlySelectable && !mSelectable) return -1; if (mTextBoundingRect.contains(pos.toPoint())) return mParentPlot->selectionTolerance() * 0.99; else return -1; } /*! Accepts the mouse event in order to emit the according click signal in the \ref mouseReleaseEvent. \seebaseclassmethod */ void QCPTextElement::mousePressEvent(QMouseEvent *event, const QVariant &details) { Q_UNUSED(details) event->accept(); } /*! Emits the \ref clicked signal if the cursor hasn't moved by more than a few pixels since the \ref mousePressEvent. \seebaseclassmethod */ void QCPTextElement::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) { if ((QPointF(event->pos()) - startPos).manhattanLength() <= 3) emit clicked(event); } /*! Emits the \ref doubleClicked signal. \seebaseclassmethod */ void QCPTextElement::mouseDoubleClickEvent(QMouseEvent *event, const QVariant &details) { Q_UNUSED(details) emit doubleClicked(event); } /*! \internal Returns the main font to be used. This is mSelectedFont if \ref setSelected is set to true, else mFont is returned. */ QFont QCPTextElement::mainFont() const { return mSelected ? mSelectedFont : mFont; } /*! \internal Returns the main color to be used. This is mSelectedTextColor if \ref setSelected is set to true, else mTextColor is returned. */ QColor QCPTextElement::mainTextColor() const { return mSelected ? mSelectedTextColor : mTextColor; } /* end of 'src/layoutelements/layoutelement-textelement.cpp' */ /* including file 'src/layoutelements/layoutelement-colorscale.cpp', size 25910 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPColorScale //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPColorScale \brief A color scale for use with color coding data such as QCPColorMap This layout element can be placed on the plot to correlate a color gradient with data values. It is usually used in combination with one or multiple \ref QCPColorMap "QCPColorMaps". \image html QCPColorScale.png The color scale can be either horizontal or vertical, as shown in the image above. The orientation and the side where the numbers appear is controlled with \ref setType. Use \ref QCPColorMap::setColorScale to connect a color map with a color scale. Once they are connected, they share their gradient, data range and data scale type (\ref setGradient, \ref setDataRange, \ref setDataScaleType). Multiple color maps may be associated with a single color scale, to make them all synchronize these properties. To have finer control over the number display and axis behaviour, you can directly access the \ref axis. See the documentation of QCPAxis for details about configuring axes. For example, if you want to change the number of automatically generated ticks, call \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolorscale-tickcount Placing a color scale next to the main axis rect works like with any other layout element: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolorscale-creation In this case we have placed it to the right of the default axis rect, so it wasn't necessary to call \ref setType, since \ref QCPAxis::atRight is already the default. The text next to the color scale can be set with \ref setLabel. For optimum appearance (like in the image above), it may be desirable to line up the axis rect and the borders of the color scale. Use a \ref QCPMarginGroup to achieve this: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolorscale-margingroup Color scales are initialized with a non-zero minimum top and bottom margin (\ref setMinimumMargins), because vertical color scales are most common and the minimum top/bottom margin makes sure it keeps some distance to the top/bottom widget border. So if you change to a horizontal color scale by setting \ref setType to \ref QCPAxis::atBottom or \ref QCPAxis::atTop, you might want to also change the minimum margins accordingly, e.g. setMinimumMargins(QMargins(6, 0, 6, 0)). */ /* start documentation of inline functions */ /*! \fn QCPAxis *QCPColorScale::axis() const Returns the internal \ref QCPAxis instance of this color scale. You can access it to alter the appearance and behaviour of the axis. \ref QCPColorScale duplicates some properties in its interface for convenience. Those are \ref setDataRange (\ref QCPAxis::setRange), \ref setDataScaleType (\ref QCPAxis::setScaleType), and the method \ref setLabel (\ref QCPAxis::setLabel). As they each are connected, it does not matter whether you use the method on the QCPColorScale or on its QCPAxis. If the type of the color scale is changed with \ref setType, the axis returned by this method will change, too, to either the left, right, bottom or top axis, depending on which type was set. */ /* end documentation of signals */ /* start documentation of signals */ /*! \fn void QCPColorScale::dataRangeChanged(const QCPRange &newRange); This signal is emitted when the data range changes. \see setDataRange */ /*! \fn void QCPColorScale::dataScaleTypeChanged(QCPAxis::ScaleType scaleType); This signal is emitted when the data scale type changes. \see setDataScaleType */ /*! \fn void QCPColorScale::gradientChanged(const QCPColorGradient &newGradient); This signal is emitted when the gradient changes. \see setGradient */ /* end documentation of signals */ /*! Constructs a new QCPColorScale. */ QCPColorScale::QCPColorScale(QCustomPlot *parentPlot) : QCPLayoutElement(parentPlot), mType( QCPAxis:: atTop), // set to atTop such that setType(QCPAxis::atRight) below doesn't skip work because it thinks it's already atRight mDataScaleType(QCPAxis::stLinear), mBarWidth(20), mAxisRect(new QCPColorScaleAxisRectPrivate(this)) { setMinimumMargins(QMargins( 0, 6, 0, 6)); // for default right color scale types, keep some room at bottom and top (important if no margin group is used) setType(QCPAxis::atRight); setDataRange(QCPRange(0, 6)); } QCPColorScale::~QCPColorScale() { delete mAxisRect; } /* undocumented getter */ QString QCPColorScale::label() const { if (!mColorAxis) { qDebug() << Q_FUNC_INFO << "internal color axis undefined"; return QString(); } return mColorAxis.data()->label(); } /* undocumented getter */ bool QCPColorScale::rangeDrag() const { if (!mAxisRect) { qDebug() << Q_FUNC_INFO << "internal axis rect was deleted"; return false; } return mAxisRect.data()->rangeDrag().testFlag(QCPAxis::orientation(mType)) && mAxisRect.data()->rangeDragAxis(QCPAxis::orientation(mType)) && mAxisRect.data()->rangeDragAxis(QCPAxis::orientation(mType))->orientation() == QCPAxis::orientation(mType); } /* undocumented getter */ bool QCPColorScale::rangeZoom() const { if (!mAxisRect) { qDebug() << Q_FUNC_INFO << "internal axis rect was deleted"; return false; } return mAxisRect.data()->rangeZoom().testFlag(QCPAxis::orientation(mType)) && mAxisRect.data()->rangeZoomAxis(QCPAxis::orientation(mType)) && mAxisRect.data()->rangeZoomAxis(QCPAxis::orientation(mType))->orientation() == QCPAxis::orientation(mType); } /*! Sets at which side of the color scale the axis is placed, and thus also its orientation. Note that after setting \a type to a different value, the axis returned by \ref axis() will be a different one. The new axis will adopt the following properties from the previous axis: The range, scale type, label and ticker (the latter will be shared and not copied). */ void QCPColorScale::setType(QCPAxis::AxisType type) { if (!mAxisRect) { qDebug() << Q_FUNC_INFO << "internal axis rect was deleted"; return; } if (mType != type) { mType = type; QCPRange rangeTransfer(0, 6); QString labelTransfer; QSharedPointer tickerTransfer; // transfer/revert some settings on old axis if it exists: bool doTransfer = (bool)mColorAxis; if (doTransfer) { rangeTransfer = mColorAxis.data()->range(); labelTransfer = mColorAxis.data()->label(); tickerTransfer = mColorAxis.data()->ticker(); mColorAxis.data()->setLabel(QString()); disconnect(mColorAxis.data(), SIGNAL(rangeChanged(QCPRange)), this, SLOT(setDataRange(QCPRange))); disconnect(mColorAxis.data(), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), this, SLOT(setDataScaleType(QCPAxis::ScaleType))); } QList allAxisTypes = QList() << QCPAxis::atLeft << QCPAxis::atRight << QCPAxis::atBottom << QCPAxis::atTop; foreach (QCPAxis::AxisType atype, allAxisTypes) { mAxisRect.data()->axis(atype)->setTicks(atype == mType); mAxisRect.data()->axis(atype)->setTickLabels(atype == mType); } // set new mColorAxis pointer: mColorAxis = mAxisRect.data()->axis(mType); // transfer settings to new axis: if (doTransfer) { mColorAxis.data()->setRange( rangeTransfer); // range transfer necessary if axis changes from vertical to horizontal or vice versa (axes with same orientation are synchronized via signals) mColorAxis.data()->setLabel(labelTransfer); mColorAxis.data()->setTicker(tickerTransfer); } connect(mColorAxis.data(), SIGNAL(rangeChanged(QCPRange)), this, SLOT(setDataRange(QCPRange))); connect(mColorAxis.data(), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), this, SLOT(setDataScaleType(QCPAxis::ScaleType))); mAxisRect.data()->setRangeDragAxes(QList() << mColorAxis.data()); } } /*! Sets the range spanned by the color gradient and that is shown by the axis in the color scale. It is equivalent to calling QCPColorMap::setDataRange on any of the connected color maps. It is also equivalent to directly accessing the \ref axis and setting its range with \ref QCPAxis::setRange. \see setDataScaleType, setGradient, rescaleDataRange */ void QCPColorScale::setDataRange(const QCPRange &dataRange) { if (mDataRange.lower != dataRange.lower || mDataRange.upper != dataRange.upper) { mDataRange = dataRange; if (mColorAxis) mColorAxis.data()->setRange(mDataRange); emit dataRangeChanged(mDataRange); } } /*! Sets the scale type of the color scale, i.e. whether values are linearly associated with colors or logarithmically. It is equivalent to calling QCPColorMap::setDataScaleType on any of the connected color maps. It is also equivalent to directly accessing the \ref axis and setting its scale type with \ref QCPAxis::setScaleType. \see setDataRange, setGradient */ void QCPColorScale::setDataScaleType(QCPAxis::ScaleType scaleType) { if (mDataScaleType != scaleType) { mDataScaleType = scaleType; if (mColorAxis) mColorAxis.data()->setScaleType(mDataScaleType); if (mDataScaleType == QCPAxis::stLogarithmic) setDataRange(mDataRange.sanitizedForLogScale()); emit dataScaleTypeChanged(mDataScaleType); } } /*! Sets the color gradient that will be used to represent data values. It is equivalent to calling QCPColorMap::setGradient on any of the connected color maps. \see setDataRange, setDataScaleType */ void QCPColorScale::setGradient(const QCPColorGradient &gradient) { if (mGradient != gradient) { mGradient = gradient; if (mAxisRect) mAxisRect.data()->mGradientImageInvalidated = true; emit gradientChanged(mGradient); } } /*! Sets the axis label of the color scale. This is equivalent to calling \ref QCPAxis::setLabel on the internal \ref axis. */ void QCPColorScale::setLabel(const QString &str) { if (!mColorAxis) { qDebug() << Q_FUNC_INFO << "internal color axis undefined"; return; } mColorAxis.data()->setLabel(str); } /*! Sets the width (or height, for horizontal color scales) the bar where the gradient is displayed will have. */ void QCPColorScale::setBarWidth(int width) { mBarWidth = width; } /*! Sets whether the user can drag the data range (\ref setDataRange). Note that \ref QCP::iRangeDrag must be in the QCustomPlot's interactions (\ref QCustomPlot::setInteractions) to allow range dragging. */ void QCPColorScale::setRangeDrag(bool enabled) { if (!mAxisRect) { qDebug() << Q_FUNC_INFO << "internal axis rect was deleted"; return; } if (enabled) mAxisRect.data()->setRangeDrag(QCPAxis::orientation(mType)); else - mAxisRect.data()->setRangeDrag(0); + mAxisRect.data()->setRangeDrag(nullptr); } /*! Sets whether the user can zoom the data range (\ref setDataRange) by scrolling the mouse wheel. Note that \ref QCP::iRangeZoom must be in the QCustomPlot's interactions (\ref QCustomPlot::setInteractions) to allow range dragging. */ void QCPColorScale::setRangeZoom(bool enabled) { if (!mAxisRect) { qDebug() << Q_FUNC_INFO << "internal axis rect was deleted"; return; } if (enabled) mAxisRect.data()->setRangeZoom(QCPAxis::orientation(mType)); else - mAxisRect.data()->setRangeZoom(0); + mAxisRect.data()->setRangeZoom(nullptr); } /*! Returns a list of all the color maps associated with this color scale. */ QList QCPColorScale::colorMaps() const { QList result; for (int i = 0; i < mParentPlot->plottableCount(); ++i) { if (QCPColorMap *cm = qobject_cast(mParentPlot->plottable(i))) if (cm->colorScale() == this) result.append(cm); } return result; } /*! Changes the data range such that all color maps associated with this color scale are fully mapped to the gradient in the data dimension. \see setDataRange */ void QCPColorScale::rescaleDataRange(bool onlyVisibleMaps) { QList maps = colorMaps(); QCPRange newRange; bool haveRange = false; QCP::SignDomain sign = QCP::sdBoth; if (mDataScaleType == QCPAxis::stLogarithmic) sign = (mDataRange.upper < 0 ? QCP::sdNegative : QCP::sdPositive); for (int i = 0; i < maps.size(); ++i) { if (!maps.at(i)->realVisibility() && onlyVisibleMaps) continue; QCPRange mapRange; if (maps.at(i)->colorScale() == this) { bool currentFoundRange = true; mapRange = maps.at(i)->data()->dataBounds(); if (sign == QCP::sdPositive) { if (mapRange.lower <= 0 && mapRange.upper > 0) mapRange.lower = mapRange.upper * 1e-3; else if (mapRange.lower <= 0 && mapRange.upper <= 0) currentFoundRange = false; } else if (sign == QCP::sdNegative) { if (mapRange.upper >= 0 && mapRange.lower < 0) mapRange.upper = mapRange.lower * 1e-3; else if (mapRange.upper >= 0 && mapRange.lower >= 0) currentFoundRange = false; } if (currentFoundRange) { if (!haveRange) newRange = mapRange; else newRange.expand(mapRange); haveRange = true; } } } if (haveRange) { if (!QCPRange::validRange( newRange)) // likely due to range being zero (plottable has only constant data in this dimension), shift current range to at least center the data { double center = (newRange.lower + newRange.upper) * 0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason if (mDataScaleType == QCPAxis::stLinear) { newRange.lower = center - mDataRange.size() / 2.0; newRange.upper = center + mDataRange.size() / 2.0; } else // mScaleType == stLogarithmic { newRange.lower = center / qSqrt(mDataRange.upper / mDataRange.lower); newRange.upper = center * qSqrt(mDataRange.upper / mDataRange.lower); } } setDataRange(newRange); } } /* inherits documentation from base class */ void QCPColorScale::update(UpdatePhase phase) { QCPLayoutElement::update(phase); if (!mAxisRect) { qDebug() << Q_FUNC_INFO << "internal axis rect was deleted"; return; } mAxisRect.data()->update(phase); switch (phase) { case upMargins: { if (mType == QCPAxis::atBottom || mType == QCPAxis::atTop) { setMaximumSize(QWIDGETSIZE_MAX, mBarWidth + mAxisRect.data()->margins().top() + mAxisRect.data()->margins().bottom() + margins().top() + margins().bottom()); setMinimumSize(0, mBarWidth + mAxisRect.data()->margins().top() + mAxisRect.data()->margins().bottom() + margins().top() + margins().bottom()); } else { setMaximumSize(mBarWidth + mAxisRect.data()->margins().left() + mAxisRect.data()->margins().right() + margins().left() + margins().right(), QWIDGETSIZE_MAX); setMinimumSize(mBarWidth + mAxisRect.data()->margins().left() + mAxisRect.data()->margins().right() + margins().left() + margins().right(), 0); } break; } case upLayout: { mAxisRect.data()->setOuterRect(rect()); break; } default: break; } } /* inherits documentation from base class */ void QCPColorScale::applyDefaultAntialiasingHint(QCPPainter *painter) const { painter->setAntialiasing(false); } /* inherits documentation from base class */ void QCPColorScale::mousePressEvent(QMouseEvent *event, const QVariant &details) { if (!mAxisRect) { qDebug() << Q_FUNC_INFO << "internal axis rect was deleted"; return; } mAxisRect.data()->mousePressEvent(event, details); } /* inherits documentation from base class */ void QCPColorScale::mouseMoveEvent(QMouseEvent *event, const QPointF &startPos) { if (!mAxisRect) { qDebug() << Q_FUNC_INFO << "internal axis rect was deleted"; return; } mAxisRect.data()->mouseMoveEvent(event, startPos); } /* inherits documentation from base class */ void QCPColorScale::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) { if (!mAxisRect) { qDebug() << Q_FUNC_INFO << "internal axis rect was deleted"; return; } mAxisRect.data()->mouseReleaseEvent(event, startPos); } /* inherits documentation from base class */ void QCPColorScale::wheelEvent(QWheelEvent *event) { if (!mAxisRect) { qDebug() << Q_FUNC_INFO << "internal axis rect was deleted"; return; } mAxisRect.data()->wheelEvent(event); } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPColorScaleAxisRectPrivate //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPColorScaleAxisRectPrivate \internal \brief An axis rect subclass for use in a QCPColorScale This is a private class and not part of the public QCustomPlot interface. It provides the axis rect functionality for the QCPColorScale class. */ /*! Creates a new instance, as a child of \a parentColorScale. */ QCPColorScaleAxisRectPrivate::QCPColorScaleAxisRectPrivate(QCPColorScale *parentColorScale) : QCPAxisRect(parentColorScale->parentPlot(), true), mParentColorScale(parentColorScale), mGradientImageInvalidated(true) { setParentLayerable(parentColorScale); setMinimumMargins(QMargins(0, 0, 0, 0)); QList allAxisTypes = QList() << QCPAxis::atBottom << QCPAxis::atTop << QCPAxis::atLeft << QCPAxis::atRight; foreach (QCPAxis::AxisType type, allAxisTypes) { axis(type)->setVisible(true); axis(type)->grid()->setVisible(false); axis(type)->setPadding(0); connect(axis(type), SIGNAL(selectionChanged(QCPAxis::SelectableParts)), this, SLOT(axisSelectionChanged(QCPAxis::SelectableParts))); connect(axis(type), SIGNAL(selectableChanged(QCPAxis::SelectableParts)), this, SLOT(axisSelectableChanged(QCPAxis::SelectableParts))); } connect(axis(QCPAxis::atLeft), SIGNAL(rangeChanged(QCPRange)), axis(QCPAxis::atRight), SLOT(setRange(QCPRange))); connect(axis(QCPAxis::atRight), SIGNAL(rangeChanged(QCPRange)), axis(QCPAxis::atLeft), SLOT(setRange(QCPRange))); connect(axis(QCPAxis::atBottom), SIGNAL(rangeChanged(QCPRange)), axis(QCPAxis::atTop), SLOT(setRange(QCPRange))); connect(axis(QCPAxis::atTop), SIGNAL(rangeChanged(QCPRange)), axis(QCPAxis::atBottom), SLOT(setRange(QCPRange))); connect(axis(QCPAxis::atLeft), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), axis(QCPAxis::atRight), SLOT(setScaleType(QCPAxis::ScaleType))); connect(axis(QCPAxis::atRight), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), axis(QCPAxis::atLeft), SLOT(setScaleType(QCPAxis::ScaleType))); connect(axis(QCPAxis::atBottom), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), axis(QCPAxis::atTop), SLOT(setScaleType(QCPAxis::ScaleType))); connect(axis(QCPAxis::atTop), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), axis(QCPAxis::atBottom), SLOT(setScaleType(QCPAxis::ScaleType))); // make layer transfers of color scale transfer to axis rect and axes // the axes must be set after axis rect, such that they appear above color gradient drawn by axis rect: connect(parentColorScale, SIGNAL(layerChanged(QCPLayer *)), this, SLOT(setLayer(QCPLayer *))); foreach (QCPAxis::AxisType type, allAxisTypes) connect(parentColorScale, SIGNAL(layerChanged(QCPLayer *)), axis(type), SLOT(setLayer(QCPLayer *))); } /*! \internal Updates the color gradient image if necessary, by calling \ref updateGradientImage, then draws it. Then the axes are drawn by calling the \ref QCPAxisRect::draw base class implementation. \seebaseclassmethod */ void QCPColorScaleAxisRectPrivate::draw(QCPPainter *painter) { if (mGradientImageInvalidated) updateGradientImage(); bool mirrorHorz = false; bool mirrorVert = false; if (mParentColorScale->mColorAxis) { mirrorHorz = mParentColorScale->mColorAxis.data()->rangeReversed() && (mParentColorScale->type() == QCPAxis::atBottom || mParentColorScale->type() == QCPAxis::atTop); mirrorVert = mParentColorScale->mColorAxis.data()->rangeReversed() && (mParentColorScale->type() == QCPAxis::atLeft || mParentColorScale->type() == QCPAxis::atRight); } painter->drawImage(rect().adjusted(0, -1, 0, -1), mGradientImage.mirrored(mirrorHorz, mirrorVert)); QCPAxisRect::draw(painter); } /*! \internal Uses the current gradient of the parent \ref QCPColorScale (specified in the constructor) to generate a gradient image. This gradient image will be used in the \ref draw method. */ void QCPColorScaleAxisRectPrivate::updateGradientImage() { if (rect().isEmpty()) return; const QImage::Format format = QImage::Format_ARGB32_Premultiplied; int n = mParentColorScale->mGradient.levelCount(); int w, h; QVector data(n); for (int i = 0; i < n; ++i) data[i] = i; if (mParentColorScale->mType == QCPAxis::atBottom || mParentColorScale->mType == QCPAxis::atTop) { w = n; h = rect().height(); mGradientImage = QImage(w, h, format); QVector pixels; for (int y = 0; y < h; ++y) pixels.append(reinterpret_cast(mGradientImage.scanLine(y))); mParentColorScale->mGradient.colorize(data.constData(), QCPRange(0, n - 1), pixels.first(), n); for (int y = 1; y < h; ++y) memcpy(pixels.at(y), pixels.first(), n * sizeof(QRgb)); } else { w = rect().width(); h = n; mGradientImage = QImage(w, h, format); for (int y = 0; y < h; ++y) { QRgb *pixels = reinterpret_cast(mGradientImage.scanLine(y)); const QRgb lineColor = mParentColorScale->mGradient.color(data[h - 1 - y], QCPRange(0, n - 1)); for (int x = 0; x < w; ++x) pixels[x] = lineColor; } } mGradientImageInvalidated = false; } /*! \internal This slot is connected to the selectionChanged signals of the four axes in the constructor. It synchronizes the selection state of the axes. */ void QCPColorScaleAxisRectPrivate::axisSelectionChanged(QCPAxis::SelectableParts selectedParts) { // axis bases of four axes shall always (de-)selected synchronously: QList allAxisTypes = QList() << QCPAxis::atBottom << QCPAxis::atTop << QCPAxis::atLeft << QCPAxis::atRight; foreach (QCPAxis::AxisType type, allAxisTypes) { if (QCPAxis *senderAxis = qobject_cast(sender())) if (senderAxis->axisType() == type) continue; if (axis(type)->selectableParts().testFlag(QCPAxis::spAxis)) { if (selectedParts.testFlag(QCPAxis::spAxis)) axis(type)->setSelectedParts(axis(type)->selectedParts() | QCPAxis::spAxis); else axis(type)->setSelectedParts(axis(type)->selectedParts() & ~QCPAxis::spAxis); } } } /*! \internal This slot is connected to the selectableChanged signals of the four axes in the constructor. It synchronizes the selectability of the axes. */ void QCPColorScaleAxisRectPrivate::axisSelectableChanged(QCPAxis::SelectableParts selectableParts) { // synchronize axis base selectability: QList allAxisTypes = QList() << QCPAxis::atBottom << QCPAxis::atTop << QCPAxis::atLeft << QCPAxis::atRight; foreach (QCPAxis::AxisType type, allAxisTypes) { if (QCPAxis *senderAxis = qobject_cast(sender())) if (senderAxis->axisType() == type) continue; if (axis(type)->selectableParts().testFlag(QCPAxis::spAxis)) { if (selectableParts.testFlag(QCPAxis::spAxis)) axis(type)->setSelectableParts(axis(type)->selectableParts() | QCPAxis::spAxis); else axis(type)->setSelectableParts(axis(type)->selectableParts() & ~QCPAxis::spAxis); } } } /* end of 'src/layoutelements/layoutelement-colorscale.cpp' */ /* including file 'src/plottables/plottable-graph.cpp', size 72363 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPGraphData //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPGraphData \brief Holds the data of one single data point for QCPGraph. The stored data is: \li \a key: coordinate on the key axis of this data point (this is the \a mainKey and the \a sortKey) \li \a value: coordinate on the value axis of this data point (this is the \a mainValue) The container for storing multiple data points is \ref QCPGraphDataContainer. It is a typedef for \ref QCPDataContainer with \ref QCPGraphData as the DataType template parameter. See the documentation there for an explanation regarding the data type's generic methods. \see QCPGraphDataContainer */ /* start documentation of inline functions */ /*! \fn double QCPGraphData::sortKey() const Returns the \a key member of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn static QCPGraphData QCPGraphData::fromSortKey(double sortKey) Returns a data point with the specified \a sortKey. All other members are set to zero. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn static static bool QCPGraphData::sortKeyIsMainKey() Since the member \a key is both the data point key coordinate and the data ordering parameter, this method returns true. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn double QCPGraphData::mainKey() const Returns the \a key member of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn double QCPGraphData::mainValue() const Returns the \a value member of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn QCPRange QCPGraphData::valueRange() const Returns a QCPRange with both lower and upper boundary set to \a value of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /* end documentation of inline functions */ /*! Constructs a data point with key and value set to zero. */ QCPGraphData::QCPGraphData() : key(0), value(0) { } /*! Constructs a data point with the specified \a key and \a value. */ QCPGraphData::QCPGraphData(double key, double value) : key(key), value(value) { } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPGraph //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPGraph \brief A plottable representing a graph in a plot. \image html QCPGraph.png Usually you create new graphs by calling QCustomPlot::addGraph. The resulting instance can be accessed via QCustomPlot::graph. To plot data, assign it with the \ref setData or \ref addData functions. Alternatively, you can also access and modify the data via the \ref data method, which returns a pointer to the internal \ref QCPGraphDataContainer. Graphs are used to display single-valued data. Single-valued means that there should only be one data point per unique key coordinate. In other words, the graph can't have \a loops. If you do want to plot non-single-valued curves, rather use the QCPCurve plottable. Gaps in the graph line can be created by adding data points with NaN as value (qQNaN() or std::numeric_limits::quiet_NaN()) in between the two data points that shall be separated. \section qcpgraph-appearance Changing the appearance The appearance of the graph is mainly determined by the line style, scatter style, brush and pen of the graph (\ref setLineStyle, \ref setScatterStyle, \ref setBrush, \ref setPen). \subsection filling Filling under or between graphs QCPGraph knows two types of fills: Normal graph fills towards the zero-value-line parallel to the key axis of the graph, and fills between two graphs, called channel fills. To enable a fill, just set a brush with \ref setBrush which is neither Qt::NoBrush nor fully transparent. By default, a normal fill towards the zero-value-line will be drawn. To set up a channel fill between this graph and another one, call \ref setChannelFillGraph with the other graph as parameter. \see QCustomPlot::addGraph, QCustomPlot::graph */ /* start of documentation of inline functions */ /*! \fn QSharedPointer QCPGraph::data() const Returns a shared pointer to the internal data storage of type \ref QCPGraphDataContainer. You may use it to directly manipulate the data, which may be more convenient and faster than using the regular \ref setData or \ref addData methods. */ /* end of documentation of inline functions */ /*! Constructs a graph which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have the same orientation. If either of these restrictions is violated, a corresponding message is printed to the debug output (qDebug), the construction is not aborted, though. The created QCPGraph is automatically registered with the QCustomPlot instance inferred from \a keyAxis. This QCustomPlot instance takes ownership of the QCPGraph, so do not delete it manually but use QCustomPlot::removePlottable() instead. To directly create a graph inside a plot, you can also use the simpler QCustomPlot::addGraph function. */ QCPGraph::QCPGraph(QCPAxis *keyAxis, QCPAxis *valueAxis) : QCPAbstractPlottable1D(keyAxis, valueAxis) { // special handling for QCPGraphs to maintain the simple graph interface: mParentPlot->registerGraph(this); setPen(QPen(Qt::blue, 0)); setBrush(Qt::NoBrush); setLineStyle(lsLine); setScatterSkip(0); - setChannelFillGraph(0); + setChannelFillGraph(nullptr); setAdaptiveSampling(true); } QCPGraph::~QCPGraph() { } /*! \overload Replaces the current data container with the provided \a data container. Since a QSharedPointer is used, multiple QCPGraphs may share the same data container safely. Modifying the data in the container will then affect all graphs that share the container. Sharing can be achieved by simply exchanging the data containers wrapped in shared pointers: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpgraph-datasharing-1 If you do not wish to share containers, but create a copy from an existing container, rather use the \ref QCPDataContainer::set method on the graph's data container directly: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpgraph-datasharing-2 \see addData */ void QCPGraph::setData(QSharedPointer data) { mDataContainer = data; } /*! \overload Replaces the current data with the provided points in \a keys and \a values. The provided vectors should have equal length. Else, the number of added points will be the size of the smallest vector. If you can guarantee that the passed data points are sorted by \a keys in ascending order, you can set \a alreadySorted to true, to improve performance by saving a sorting run. \see addData */ void QCPGraph::setData(const QVector &keys, const QVector &values, bool alreadySorted) { mDataContainer->clear(); addData(keys, values, alreadySorted); } /*! Sets how the single data points are connected in the plot. For scatter-only plots, set \a ls to \ref lsNone and \ref setScatterStyle to the desired scatter style. \see setScatterStyle */ void QCPGraph::setLineStyle(LineStyle ls) { mLineStyle = ls; } /*! Sets the visual appearance of single data points in the plot. If set to \ref QCPScatterStyle::ssNone, no scatter points are drawn (e.g. for line-only-plots with appropriate line style). \see QCPScatterStyle, setLineStyle */ void QCPGraph::setScatterStyle(const QCPScatterStyle &style) { mScatterStyle = style; } /*! If scatters are displayed (scatter style not \ref QCPScatterStyle::ssNone), \a skip number of scatter points are skipped/not drawn after every drawn scatter point. This can be used to make the data appear sparser while for example still having a smooth line, and to improve performance for very high density plots. If \a skip is set to 0 (default), all scatter points are drawn. \see setScatterStyle */ void QCPGraph::setScatterSkip(int skip) { mScatterSkip = qMax(0, skip); } /*! Sets the target graph for filling the area between this graph and \a targetGraph with the current brush (\ref setBrush). When \a targetGraph is set to 0, a normal graph fill to the zero-value-line will be shown. To disable any filling, set the brush to Qt::NoBrush. \see setBrush */ void QCPGraph::setChannelFillGraph(QCPGraph *targetGraph) { // prevent setting channel target to this graph itself: if (targetGraph == this) { qDebug() << Q_FUNC_INFO << "targetGraph is this graph itself"; - mChannelFillGraph = 0; + mChannelFillGraph = nullptr; return; } // prevent setting channel target to a graph not in the plot: if (targetGraph && targetGraph->mParentPlot != mParentPlot) { qDebug() << Q_FUNC_INFO << "targetGraph not in same plot"; - mChannelFillGraph = 0; + mChannelFillGraph = nullptr; return; } mChannelFillGraph = targetGraph; } /*! Sets whether adaptive sampling shall be used when plotting this graph. QCustomPlot's adaptive sampling technique can drastically improve the replot performance for graphs with a larger number of points (e.g. above 10,000), without notably changing the appearance of the graph. By default, adaptive sampling is enabled. Even if enabled, QCustomPlot decides whether adaptive sampling shall actually be used on a per-graph basis. So leaving adaptive sampling enabled has no disadvantage in almost all cases. \image html adaptive-sampling-line.png "A line plot of 500,000 points without and with adaptive sampling" As can be seen, line plots experience no visual degradation from adaptive sampling. Outliers are reproduced reliably, as well as the overall shape of the data set. The replot time reduces dramatically though. This allows QCustomPlot to display large amounts of data in realtime. \image html adaptive-sampling-scatter.png "A scatter plot of 100,000 points without and with adaptive sampling" Care must be taken when using high-density scatter plots in combination with adaptive sampling. The adaptive sampling algorithm treats scatter plots more carefully than line plots which still gives a significant reduction of replot times, but not quite as much as for line plots. This is because scatter plots inherently need more data points to be preserved in order to still resemble the original, non-adaptive-sampling plot. As shown above, the results still aren't quite identical, as banding occurs for the outer data points. This is in fact intentional, such that the boundaries of the data cloud stay visible to the viewer. How strong the banding appears, depends on the point density, i.e. the number of points in the plot. For some situations with scatter plots it might thus be desirable to manually turn adaptive sampling off. For example, when saving the plot to disk. This can be achieved by setting \a enabled to false before issuing a command like \ref QCustomPlot::savePng, and setting \a enabled back to true afterwards. */ void QCPGraph::setAdaptiveSampling(bool enabled) { mAdaptiveSampling = enabled; } /*! \overload Adds the provided points in \a keys and \a values to the current data. The provided vectors should have equal length. Else, the number of added points will be the size of the smallest vector. If you can guarantee that the passed data points are sorted by \a keys in ascending order, you can set \a alreadySorted to true, to improve performance by saving a sorting run. Alternatively, you can also access and modify the data directly via the \ref data method, which returns a pointer to the internal data container. */ void QCPGraph::addData(const QVector &keys, const QVector &values, bool alreadySorted) { if (keys.size() != values.size()) qDebug() << Q_FUNC_INFO << "keys and values have different sizes:" << keys.size() << values.size(); const int n = qMin(keys.size(), values.size()); QVector tempData(n); QVector::iterator it = tempData.begin(); const QVector::iterator itEnd = tempData.end(); int i = 0; while (it != itEnd) { it->key = keys[i]; it->value = values[i]; ++it; ++i; } mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write } /*! \overload Adds the provided data point as \a key and \a value to the current data. Alternatively, you can also access and modify the data directly via the \ref data method, which returns a pointer to the internal data container. */ void QCPGraph::addData(double key, double value) { mDataContainer->add(QCPGraphData(key, value)); } /* inherits documentation from base class */ double QCPGraph::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty()) return -1; if (!mKeyAxis || !mValueAxis) return -1; if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint())) { QCPGraphDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd(); double result = pointDistance(pos, closestDataPoint); if (details) { int pointIndex = closestDataPoint - mDataContainer->constBegin(); details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex + 1))); } return result; } else return -1; } /* inherits documentation from base class */ QCPRange QCPGraph::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const { return mDataContainer->keyRange(foundRange, inSignDomain); } /* inherits documentation from base class */ QCPRange QCPGraph::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const { return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange); } /* inherits documentation from base class */ void QCPGraph::draw(QCPPainter *painter) { if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; } if (mKeyAxis.data()->range().size() <= 0 || mDataContainer->isEmpty()) return; if (mLineStyle == lsNone && mScatterStyle.isNone()) return; QVector lines, scatters; // line and (if necessary) scatter pixel coordinates will be stored here while iterating over segments // loop over and draw segments of unselected/selected data: QList selectedSegments, unselectedSegments, allSegments; getDataSegments(selectedSegments, unselectedSegments); allSegments << unselectedSegments << selectedSegments; for (int i = 0; i < allSegments.size(); ++i) { bool isSelectedSegment = i >= unselectedSegments.size(); // get line pixel points appropriate to line style: QCPDataRange lineDataRange = isSelectedSegment ? allSegments.at(i) : allSegments.at(i).adjusted( -1, 1); // unselected segments extend lines to bordering selected data point (safe to exceed total data bounds in first/last segment, getLines takes care) getLines(&lines, lineDataRange); // check data validity if flag set: #ifdef QCUSTOMPLOT_CHECK_DATA QCPGraphDataContainer::const_iterator it; for (it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it) { if (QCP::isInvalidData(it->key, it->value)) qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "invalid." << "Plottable name:" << name(); } #endif // draw fill of graph: if (isSelectedSegment && mSelectionDecorator) mSelectionDecorator->applyBrush(painter); else painter->setBrush(mBrush); painter->setPen(Qt::NoPen); drawFill(painter, &lines); // draw line: if (mLineStyle != lsNone) { if (isSelectedSegment && mSelectionDecorator) mSelectionDecorator->applyPen(painter); else painter->setPen(mPen); painter->setBrush(Qt::NoBrush); if (mLineStyle == lsImpulse) drawImpulsePlot(painter, lines); else drawLinePlot(painter, lines); // also step plots can be drawn as a line plot } // draw scatters: QCPScatterStyle finalScatterStyle = mScatterStyle; if (isSelectedSegment && mSelectionDecorator) finalScatterStyle = mSelectionDecorator->getFinalScatterStyle(mScatterStyle); if (!finalScatterStyle.isNone()) { getScatters(&scatters, allSegments.at(i)); drawScatterPlot(painter, scatters, finalScatterStyle); } } // draw other selection decoration that isn't just line/scatter pens and brushes: if (mSelectionDecorator) mSelectionDecorator->drawDecoration(painter, selection()); } /* inherits documentation from base class */ void QCPGraph::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const { // draw fill: if (mBrush.style() != Qt::NoBrush) { applyFillAntialiasingHint(painter); painter->fillRect(QRectF(rect.left(), rect.top() + rect.height() / 2.0, rect.width(), rect.height() / 3.0), mBrush); } // draw line vertically centered: if (mLineStyle != lsNone) { applyDefaultAntialiasingHint(painter); painter->setPen(mPen); painter->drawLine( QLineF(rect.left(), rect.top() + rect.height() / 2.0, rect.right() + 5, rect.top() + rect.height() / 2.0)); // +5 on x2 else last segment is missing from dashed/dotted pens } // draw scatter symbol: if (!mScatterStyle.isNone()) { applyScattersAntialiasingHint(painter); // scale scatter pixmap if it's too large to fit in legend icon rect: if (mScatterStyle.shape() == QCPScatterStyle::ssPixmap && (mScatterStyle.pixmap().size().width() > rect.width() || mScatterStyle.pixmap().size().height() > rect.height())) { QCPScatterStyle scaledStyle(mScatterStyle); scaledStyle.setPixmap( scaledStyle.pixmap().scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); scaledStyle.applyTo(painter, mPen); scaledStyle.drawShape(painter, QRectF(rect).center()); } else { mScatterStyle.applyTo(painter, mPen); mScatterStyle.drawShape(painter, QRectF(rect).center()); } } } /*! \internal This method retrieves an optimized set of data points via \ref getOptimizedLineData, an branches out to the line style specific functions such as \ref dataToLines, \ref dataToStepLeftLines, etc. according to the line style of the graph. \a lines will be filled with points in pixel coordinates, that can be drawn with the according draw functions like \ref drawLinePlot and \ref drawImpulsePlot. The points returned in \a lines aren't necessarily the original data points. For example, step line styles require additional points to form the steps when drawn. If the line style of the graph is \ref lsNone, the \a lines vector will be empty. \a dataRange specifies the beginning and ending data indices that will be taken into account for conversion. In this function, the specified range may exceed the total data bounds without harm: a correspondingly trimmed data range will be used. This takes the burden off the user of this function to check for valid indices in \a dataRange, e.g. when extending ranges coming from \ref getDataSegments. \see getScatters */ void QCPGraph::getLines(QVector *lines, const QCPDataRange &dataRange) const { if (!lines) return; QCPGraphDataContainer::const_iterator begin, end; getVisibleDataBounds(begin, end, dataRange); if (begin == end) { lines->clear(); return; } QVector lineData; if (mLineStyle != lsNone) getOptimizedLineData(&lineData, begin, end); switch (mLineStyle) { case lsNone: lines->clear(); break; case lsLine: *lines = dataToLines(lineData); break; case lsStepLeft: *lines = dataToStepLeftLines(lineData); break; case lsStepRight: *lines = dataToStepRightLines(lineData); break; case lsStepCenter: *lines = dataToStepCenterLines(lineData); break; case lsImpulse: *lines = dataToImpulseLines(lineData); break; } } /*! \internal This method retrieves an optimized set of data points via \ref getOptimizedScatterData and then converts them to pixel coordinates. The resulting points are returned in \a scatters, and can be passed to \ref drawScatterPlot. \a dataRange specifies the beginning and ending data indices that will be taken into account for conversion. In this function, the specified range may exceed the total data bounds without harm: a correspondingly trimmed data range will be used. This takes the burden off the user of this function to check for valid indices in \a dataRange, e.g. when extending ranges coming from \ref getDataSegments. */ void QCPGraph::getScatters(QVector *scatters, const QCPDataRange &dataRange) const { if (!scatters) return; QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; scatters->clear(); return; } QCPGraphDataContainer::const_iterator begin, end; getVisibleDataBounds(begin, end, dataRange); if (begin == end) { scatters->clear(); return; } QVector data; getOptimizedScatterData(&data, begin, end); scatters->resize(data.size()); if (keyAxis->orientation() == Qt::Vertical) { for (int i = 0; i < data.size(); ++i) { if (!qIsNaN(data.at(i).value)) { (*scatters)[i].setX(valueAxis->coordToPixel(data.at(i).value)); (*scatters)[i].setY(keyAxis->coordToPixel(data.at(i).key)); } } } else { for (int i = 0; i < data.size(); ++i) { if (!qIsNaN(data.at(i).value)) { (*scatters)[i].setX(keyAxis->coordToPixel(data.at(i).key)); (*scatters)[i].setY(valueAxis->coordToPixel(data.at(i).value)); } } } } /*! \internal Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel coordinate points which are suitable for drawing the line style \ref lsLine. The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a getLines if the line style is set accordingly. \see dataToStepLeftLines, dataToStepRightLines, dataToStepCenterLines, dataToImpulseLines, getLines, drawLinePlot */ QVector QCPGraph::dataToLines(const QVector &data) const { QVector result; QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; } result.reserve(data.size() + 2); // added 2 to reserve memory for lower/upper fill base points that might be needed for fill result.resize(data.size()); // transform data points to pixels: if (keyAxis->orientation() == Qt::Vertical) { for (int i = 0; i < data.size(); ++i) { result[i].setX(valueAxis->coordToPixel(data.at(i).value)); result[i].setY(keyAxis->coordToPixel(data.at(i).key)); } } else // key axis is horizontal { for (int i = 0; i < data.size(); ++i) { result[i].setX(keyAxis->coordToPixel(data.at(i).key)); result[i].setY(valueAxis->coordToPixel(data.at(i).value)); } } return result; } /*! \internal Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel coordinate points which are suitable for drawing the line style \ref lsStepLeft. The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a getLines if the line style is set accordingly. \see dataToLines, dataToStepRightLines, dataToStepCenterLines, dataToImpulseLines, getLines, drawLinePlot */ QVector QCPGraph::dataToStepLeftLines(const QVector &data) const { QVector result; QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; } result.reserve(data.size() * 2 + 2); // added 2 to reserve memory for lower/upper fill base points that might be needed for fill result.resize(data.size() * 2); // calculate steps from data and transform to pixel coordinates: if (keyAxis->orientation() == Qt::Vertical) { double lastValue = valueAxis->coordToPixel(data.first().value); for (int i = 0; i < data.size(); ++i) { const double key = keyAxis->coordToPixel(data.at(i).key); result[i * 2 + 0].setX(lastValue); result[i * 2 + 0].setY(key); lastValue = valueAxis->coordToPixel(data.at(i).value); result[i * 2 + 1].setX(lastValue); result[i * 2 + 1].setY(key); } } else // key axis is horizontal { double lastValue = valueAxis->coordToPixel(data.first().value); for (int i = 0; i < data.size(); ++i) { const double key = keyAxis->coordToPixel(data.at(i).key); result[i * 2 + 0].setX(key); result[i * 2 + 0].setY(lastValue); lastValue = valueAxis->coordToPixel(data.at(i).value); result[i * 2 + 1].setX(key); result[i * 2 + 1].setY(lastValue); } } return result; } /*! \internal Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel coordinate points which are suitable for drawing the line style \ref lsStepRight. The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a getLines if the line style is set accordingly. \see dataToLines, dataToStepLeftLines, dataToStepCenterLines, dataToImpulseLines, getLines, drawLinePlot */ QVector QCPGraph::dataToStepRightLines(const QVector &data) const { QVector result; QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; } result.reserve(data.size() * 2 + 2); // added 2 to reserve memory for lower/upper fill base points that might be needed for fill result.resize(data.size() * 2); // calculate steps from data and transform to pixel coordinates: if (keyAxis->orientation() == Qt::Vertical) { double lastKey = keyAxis->coordToPixel(data.first().key); for (int i = 0; i < data.size(); ++i) { const double value = valueAxis->coordToPixel(data.at(i).value); result[i * 2 + 0].setX(value); result[i * 2 + 0].setY(lastKey); lastKey = keyAxis->coordToPixel(data.at(i).key); result[i * 2 + 1].setX(value); result[i * 2 + 1].setY(lastKey); } } else // key axis is horizontal { double lastKey = keyAxis->coordToPixel(data.first().key); for (int i = 0; i < data.size(); ++i) { const double value = valueAxis->coordToPixel(data.at(i).value); result[i * 2 + 0].setX(lastKey); result[i * 2 + 0].setY(value); lastKey = keyAxis->coordToPixel(data.at(i).key); result[i * 2 + 1].setX(lastKey); result[i * 2 + 1].setY(value); } } return result; } /*! \internal Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel coordinate points which are suitable for drawing the line style \ref lsStepCenter. The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a getLines if the line style is set accordingly. \see dataToLines, dataToStepLeftLines, dataToStepRightLines, dataToImpulseLines, getLines, drawLinePlot */ QVector QCPGraph::dataToStepCenterLines(const QVector &data) const { QVector result; QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; } result.reserve(data.size() * 2 + 2); // added 2 to reserve memory for lower/upper fill base points that might be needed for fill result.resize(data.size() * 2); // calculate steps from data and transform to pixel coordinates: if (keyAxis->orientation() == Qt::Vertical) { double lastKey = keyAxis->coordToPixel(data.first().key); double lastValue = valueAxis->coordToPixel(data.first().value); result[0].setX(lastValue); result[0].setY(lastKey); for (int i = 1; i < data.size(); ++i) { const double key = (keyAxis->coordToPixel(data.at(i).key) + lastKey) * 0.5; result[i * 2 - 1].setX(lastValue); result[i * 2 - 1].setY(key); lastValue = valueAxis->coordToPixel(data.at(i).value); lastKey = keyAxis->coordToPixel(data.at(i).key); result[i * 2 + 0].setX(lastValue); result[i * 2 + 0].setY(key); } result[data.size() * 2 - 1].setX(lastValue); result[data.size() * 2 - 1].setY(lastKey); } else // key axis is horizontal { double lastKey = keyAxis->coordToPixel(data.first().key); double lastValue = valueAxis->coordToPixel(data.first().value); result[0].setX(lastKey); result[0].setY(lastValue); for (int i = 1; i < data.size(); ++i) { const double key = (keyAxis->coordToPixel(data.at(i).key) + lastKey) * 0.5; result[i * 2 - 1].setX(key); result[i * 2 - 1].setY(lastValue); lastValue = valueAxis->coordToPixel(data.at(i).value); lastKey = keyAxis->coordToPixel(data.at(i).key); result[i * 2 + 0].setX(key); result[i * 2 + 0].setY(lastValue); } result[data.size() * 2 - 1].setX(lastKey); result[data.size() * 2 - 1].setY(lastValue); } return result; } /*! \internal Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel coordinate points which are suitable for drawing the line style \ref lsImpulse. The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a getLines if the line style is set accordingly. \see dataToLines, dataToStepLeftLines, dataToStepRightLines, dataToStepCenterLines, getLines, drawImpulsePlot */ QVector QCPGraph::dataToImpulseLines(const QVector &data) const { QVector result; QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; } result.resize(data.size() * 2); // no need to reserve 2 extra points because impulse plot has no fill // transform data points to pixels: if (keyAxis->orientation() == Qt::Vertical) { for (int i = 0; i < data.size(); ++i) { const double key = keyAxis->coordToPixel(data.at(i).key); result[i * 2 + 0].setX(valueAxis->coordToPixel(0)); result[i * 2 + 0].setY(key); result[i * 2 + 1].setX(valueAxis->coordToPixel(data.at(i).value)); result[i * 2 + 1].setY(key); } } else // key axis is horizontal { for (int i = 0; i < data.size(); ++i) { const double key = keyAxis->coordToPixel(data.at(i).key); result[i * 2 + 0].setX(key); result[i * 2 + 0].setY(valueAxis->coordToPixel(0)); result[i * 2 + 1].setX(key); result[i * 2 + 1].setY(valueAxis->coordToPixel(data.at(i).value)); } } return result; } /*! \internal Draws the fill of the graph using the specified \a painter, with the currently set brush. \a lines contains the points of the graph line, in pixel coordinates. If the fill is a normal fill towards the zero-value-line, only the points in \a lines are required and two extra points at the zero-value-line, which are added by \ref addFillBasePoints and removed by \ref removeFillBasePoints after the fill drawing is done. On the other hand if the fill is a channel fill between this QCPGraph and another QCPGraph (\a mChannelFillGraph), the more complex polygon is calculated with the \ref getChannelFillPolygon function, and then drawn. \see drawLinePlot, drawImpulsePlot, drawScatterPlot */ void QCPGraph::drawFill(QCPPainter *painter, QVector *lines) const { if (mLineStyle == lsImpulse) return; // fill doesn't make sense for impulse plot if (painter->brush().style() == Qt::NoBrush || painter->brush().color().alpha() == 0) return; applyFillAntialiasingHint(painter); if (!mChannelFillGraph) { // draw base fill under graph, fill goes all the way to the zero-value-line: addFillBasePoints(lines); painter->drawPolygon(QPolygonF(*lines)); removeFillBasePoints(lines); } else { // draw channel fill between this graph and mChannelFillGraph: painter->drawPolygon(getChannelFillPolygon(lines)); } } /*! \internal Draws scatter symbols at every point passed in \a scatters, given in pixel coordinates. The scatters will be drawn with \a painter and have the appearance as specified in \a style. \see drawLinePlot, drawImpulsePlot */ void QCPGraph::drawScatterPlot(QCPPainter *painter, const QVector &scatters, const QCPScatterStyle &style) const { applyScattersAntialiasingHint(painter); style.applyTo(painter, mPen); for (int i = 0; i < scatters.size(); ++i) style.drawShape(painter, scatters.at(i).x(), scatters.at(i).y()); } /*! \internal Draws lines between the points in \a lines, given in pixel coordinates. \see drawScatterPlot, drawImpulsePlot, QCPAbstractPlottable1D::drawPolyline */ void QCPGraph::drawLinePlot(QCPPainter *painter, const QVector &lines) const { if (painter->pen().style() != Qt::NoPen && painter->pen().color().alpha() != 0) { applyDefaultAntialiasingHint(painter); drawPolyline(painter, lines); } } /*! \internal Draws impulses from the provided data, i.e. it connects all line pairs in \a lines, given in pixel coordinates. The \a lines necessary for impulses are generated by \ref dataToImpulseLines from the regular graph data points. \see drawLinePlot, drawScatterPlot */ void QCPGraph::drawImpulsePlot(QCPPainter *painter, const QVector &lines) const { if (painter->pen().style() != Qt::NoPen && painter->pen().color().alpha() != 0) { applyDefaultAntialiasingHint(painter); QPen oldPen = painter->pen(); QPen newPen = painter->pen(); newPen.setCapStyle(Qt::FlatCap); // so impulse line doesn't reach beyond zero-line painter->setPen(newPen); painter->drawLines(lines); painter->setPen(oldPen); } } /*! \internal Returns via \a lineData the data points that need to be visualized for this graph when plotting graph lines, taking into consideration the currently visible axis ranges and, if \ref setAdaptiveSampling is enabled, local point densities. The considered data can be restricted further by \a begin and \a end, e.g. to only plot a certain segment of the data (see \ref getDataSegments). This method is used by \ref getLines to retrieve the basic working set of data. \see getOptimizedScatterData */ void QCPGraph::getOptimizedLineData(QVector *lineData, const QCPGraphDataContainer::const_iterator &begin, const QCPGraphDataContainer::const_iterator &end) const { if (!lineData) return; QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; } if (begin == end) return; int dataCount = end - begin; int maxCount = std::numeric_limits::max(); if (mAdaptiveSampling) { double keyPixelSpan = qAbs(keyAxis->coordToPixel(begin->key) - keyAxis->coordToPixel((end - 1)->key)); if (2 * keyPixelSpan + 2 < (double)std::numeric_limits::max()) maxCount = 2 * keyPixelSpan + 2; } if (mAdaptiveSampling && dataCount >= maxCount) // use adaptive sampling only if there are at least two points per pixel on average { QCPGraphDataContainer::const_iterator it = begin; double minValue = it->value; double maxValue = it->value; QCPGraphDataContainer::const_iterator currentIntervalFirstPoint = it; int reversedFactor = keyAxis->pixelOrientation(); // is used to calculate keyEpsilon pixel into the correct direction int reversedRound = reversedFactor == -1 ? 1 : 0; // is used to switch between floor (normal) and ceil (reversed) rounding of currentIntervalStartKey double currentIntervalStartKey = keyAxis->pixelToCoord((int)(keyAxis->coordToPixel(begin->key) + reversedRound)); double lastIntervalEndKey = currentIntervalStartKey; double keyEpsilon = qAbs(currentIntervalStartKey - keyAxis->pixelToCoord( keyAxis->coordToPixel(currentIntervalStartKey) + 1.0 * reversedFactor)); // interval of one pixel on screen when mapped to plot key coordinates bool keyEpsilonVariable = keyAxis->scaleType() == QCPAxis:: stLogarithmic; // indicates whether keyEpsilon needs to be updated after every interval (for log axes) int intervalDataCount = 1; ++it; // advance iterator to second data point because adaptive sampling works in 1 point retrospect while (it != end) { if (it->key < currentIntervalStartKey + keyEpsilon) // data point is still within same pixel, so skip it and expand value span of this cluster if necessary { if (it->value < minValue) minValue = it->value; else if (it->value > maxValue) maxValue = it->value; ++intervalDataCount; } else // new pixel interval started { if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them to a cluster { if (lastIntervalEndKey < currentIntervalStartKey - keyEpsilon) // last point is further away, so first point of this cluster must be at a real data point lineData->append( QCPGraphData(currentIntervalStartKey + keyEpsilon * 0.2, currentIntervalFirstPoint->value)); lineData->append(QCPGraphData(currentIntervalStartKey + keyEpsilon * 0.25, minValue)); lineData->append(QCPGraphData(currentIntervalStartKey + keyEpsilon * 0.75, maxValue)); if (it->key > currentIntervalStartKey + keyEpsilon * 2) // new pixel started further away from previous cluster, so make sure the last point of the cluster is at a real data point lineData->append(QCPGraphData(currentIntervalStartKey + keyEpsilon * 0.8, (it - 1)->value)); } else lineData->append(QCPGraphData(currentIntervalFirstPoint->key, currentIntervalFirstPoint->value)); lastIntervalEndKey = (it - 1)->key; minValue = it->value; maxValue = it->value; currentIntervalFirstPoint = it; currentIntervalStartKey = keyAxis->pixelToCoord((int)(keyAxis->coordToPixel(it->key) + reversedRound)); if (keyEpsilonVariable) keyEpsilon = qAbs( currentIntervalStartKey - keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey) + 1.0 * reversedFactor)); intervalDataCount = 1; } ++it; } // handle last interval: if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them to a cluster { if (lastIntervalEndKey < currentIntervalStartKey - keyEpsilon) // last point wasn't a cluster, so first point of this cluster must be at a real data point lineData->append( QCPGraphData(currentIntervalStartKey + keyEpsilon * 0.2, currentIntervalFirstPoint->value)); lineData->append(QCPGraphData(currentIntervalStartKey + keyEpsilon * 0.25, minValue)); lineData->append(QCPGraphData(currentIntervalStartKey + keyEpsilon * 0.75, maxValue)); } else lineData->append(QCPGraphData(currentIntervalFirstPoint->key, currentIntervalFirstPoint->value)); } else // don't use adaptive sampling algorithm, transfer points one-to-one from the data container into the output { QCPGraphDataContainer::const_iterator it = begin; lineData->reserve(dataCount + 2); // +2 for possible fill end points while (it != end) { lineData->append(*it); ++it; } } } /*! \internal Returns via \a scatterData the data points that need to be visualized for this graph when plotting scatter points, taking into consideration the currently visible axis ranges and, if \ref setAdaptiveSampling is enabled, local point densities. The considered data can be restricted further by \a begin and \a end, e.g. to only plot a certain segment of the data (see \ref getDataSegments). This method is used by \ref getScatters to retrieve the basic working set of data. \see getOptimizedLineData */ void QCPGraph::getOptimizedScatterData(QVector *scatterData, QCPGraphDataContainer::const_iterator begin, QCPGraphDataContainer::const_iterator end) const { if (!scatterData) return; QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; } const int scatterModulo = mScatterSkip + 1; const bool doScatterSkip = mScatterSkip > 0; int beginIndex = begin - mDataContainer->constBegin(); int endIndex = end - mDataContainer->constBegin(); while (doScatterSkip && begin != end && beginIndex % scatterModulo != 0) // advance begin iterator to first non-skipped scatter { ++beginIndex; ++begin; } if (begin == end) return; int dataCount = end - begin; int maxCount = std::numeric_limits::max(); if (mAdaptiveSampling) { int keyPixelSpan = qAbs(keyAxis->coordToPixel(begin->key) - keyAxis->coordToPixel((end - 1)->key)); maxCount = 2 * keyPixelSpan + 2; } if (mAdaptiveSampling && dataCount >= maxCount) // use adaptive sampling only if there are at least two points per pixel on average { double valueMaxRange = valueAxis->range().upper; double valueMinRange = valueAxis->range().lower; QCPGraphDataContainer::const_iterator it = begin; int itIndex = beginIndex; double minValue = it->value; double maxValue = it->value; QCPGraphDataContainer::const_iterator minValueIt = it; QCPGraphDataContainer::const_iterator maxValueIt = it; QCPGraphDataContainer::const_iterator currentIntervalStart = it; int reversedFactor = keyAxis->pixelOrientation(); // is used to calculate keyEpsilon pixel into the correct direction int reversedRound = reversedFactor == -1 ? 1 : 0; // is used to switch between floor (normal) and ceil (reversed) rounding of currentIntervalStartKey double currentIntervalStartKey = keyAxis->pixelToCoord((int)(keyAxis->coordToPixel(begin->key) + reversedRound)); double keyEpsilon = qAbs(currentIntervalStartKey - keyAxis->pixelToCoord( keyAxis->coordToPixel(currentIntervalStartKey) + 1.0 * reversedFactor)); // interval of one pixel on screen when mapped to plot key coordinates bool keyEpsilonVariable = keyAxis->scaleType() == QCPAxis:: stLogarithmic; // indicates whether keyEpsilon needs to be updated after every interval (for log axes) int intervalDataCount = 1; // advance iterator to second (non-skipped) data point because adaptive sampling works in 1 point retrospect: if (!doScatterSkip) ++it; else { itIndex += scatterModulo; if (itIndex < endIndex) // make sure we didn't jump over end it += scatterModulo; else { it = end; itIndex = endIndex; } } // main loop over data points: while (it != end) { if (it->key < currentIntervalStartKey + keyEpsilon) // data point is still within same pixel, so skip it and expand value span of this pixel if necessary { if (it->value < minValue && it->value > valueMinRange && it->value < valueMaxRange) { minValue = it->value; minValueIt = it; } else if (it->value > maxValue && it->value > valueMinRange && it->value < valueMaxRange) { maxValue = it->value; maxValueIt = it; } ++intervalDataCount; } else // new pixel started { if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them { // determine value pixel span and add as many points in interval to maintain certain vertical data density (this is specific to scatter plot): double valuePixelSpan = qAbs(valueAxis->coordToPixel(minValue) - valueAxis->coordToPixel(maxValue)); int dataModulo = qMax( 1, qRound(intervalDataCount / (valuePixelSpan / 4.0))); // approximately every 4 value pixels one data point on average QCPGraphDataContainer::const_iterator intervalIt = currentIntervalStart; int c = 0; while (intervalIt != it) { if ((c % dataModulo == 0 || intervalIt == minValueIt || intervalIt == maxValueIt) && intervalIt->value > valueMinRange && intervalIt->value < valueMaxRange) scatterData->append(*intervalIt); ++c; if (!doScatterSkip) ++intervalIt; else intervalIt += scatterModulo; // since we know indices of "currentIntervalStart", "intervalIt" and "it" are multiples of scatterModulo, we can't accidentally jump over "it" here } } else if (currentIntervalStart->value > valueMinRange && currentIntervalStart->value < valueMaxRange) scatterData->append(*currentIntervalStart); minValue = it->value; maxValue = it->value; currentIntervalStart = it; currentIntervalStartKey = keyAxis->pixelToCoord((int)(keyAxis->coordToPixel(it->key) + reversedRound)); if (keyEpsilonVariable) keyEpsilon = qAbs( currentIntervalStartKey - keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey) + 1.0 * reversedFactor)); intervalDataCount = 1; } // advance to next data point: if (!doScatterSkip) ++it; else { itIndex += scatterModulo; if (itIndex < endIndex) // make sure we didn't jump over end it += scatterModulo; else { it = end; itIndex = endIndex; } } } // handle last interval: if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them { // determine value pixel span and add as many points in interval to maintain certain vertical data density (this is specific to scatter plot): double valuePixelSpan = qAbs(valueAxis->coordToPixel(minValue) - valueAxis->coordToPixel(maxValue)); int dataModulo = qMax(1, qRound(intervalDataCount / (valuePixelSpan / 4.0))); // approximately every 4 value pixels one data point on average QCPGraphDataContainer::const_iterator intervalIt = currentIntervalStart; int intervalItIndex = intervalIt - mDataContainer->constBegin(); int c = 0; while (intervalIt != it) { if ((c % dataModulo == 0 || intervalIt == minValueIt || intervalIt == maxValueIt) && intervalIt->value > valueMinRange && intervalIt->value < valueMaxRange) scatterData->append(*intervalIt); ++c; if (!doScatterSkip) ++intervalIt; else // here we can't guarantee that adding scatterModulo doesn't exceed "it" (because "it" is equal to "end" here, and "end" isn't scatterModulo-aligned), so check via index comparison: { intervalItIndex += scatterModulo; if (intervalItIndex < itIndex) intervalIt += scatterModulo; else { intervalIt = it; intervalItIndex = itIndex; } } } } else if (currentIntervalStart->value > valueMinRange && currentIntervalStart->value < valueMaxRange) scatterData->append(*currentIntervalStart); } else // don't use adaptive sampling algorithm, transfer points one-to-one from the data container into the output { QCPGraphDataContainer::const_iterator it = begin; int itIndex = beginIndex; scatterData->reserve(dataCount); while (it != end) { scatterData->append(*it); // advance to next data point: if (!doScatterSkip) ++it; else { itIndex += scatterModulo; if (itIndex < endIndex) it += scatterModulo; else { it = end; itIndex = endIndex; } } } } } /*! This method outputs the currently visible data range via \a begin and \a end. The returned range will also never exceed \a rangeRestriction. This method takes into account that the drawing of data lines at the axis rect border always requires the points just outside the visible axis range. So \a begin and \a end may actually indicate a range that contains one additional data point to the left and right of the visible axis range. */ void QCPGraph::getVisibleDataBounds(QCPGraphDataContainer::const_iterator &begin, QCPGraphDataContainer::const_iterator &end, const QCPDataRange &rangeRestriction) const { if (rangeRestriction.isEmpty()) { end = mDataContainer->constEnd(); begin = end; } else { QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; } // get visible data range: begin = mDataContainer->findBegin(keyAxis->range().lower); end = mDataContainer->findEnd(keyAxis->range().upper); // limit lower/upperEnd to rangeRestriction: mDataContainer->limitIteratorsToDataRange( begin, end, rangeRestriction); // this also ensures rangeRestriction outside data bounds doesn't break anything } } /*! \internal The line vector generated by e.g. \ref getLines describes only the line that connects the data points. If the graph needs to be filled, two additional points need to be added at the value-zero-line in the lower and upper key positions of the graph. This function calculates these points and adds them to the end of \a lineData. Since the fill is typically drawn before the line stroke, these added points need to be removed again after the fill is done, with the removeFillBasePoints function. The expanding of \a lines by two points will not cause unnecessary memory reallocations, because the data vector generation functions (e.g. \ref getLines) reserve two extra points when they allocate memory for \a lines. \see removeFillBasePoints, lowerFillBasePoint, upperFillBasePoint */ void QCPGraph::addFillBasePoints(QVector *lines) const { if (!mKeyAxis) { qDebug() << Q_FUNC_INFO << "invalid key axis"; return; } if (!lines) { qDebug() << Q_FUNC_INFO << "passed null as lineData"; return; } if (lines->isEmpty()) return; // append points that close the polygon fill at the key axis: if (mKeyAxis.data()->orientation() == Qt::Vertical) { *lines << upperFillBasePoint(lines->last().y()); *lines << lowerFillBasePoint(lines->first().y()); } else { *lines << upperFillBasePoint(lines->last().x()); *lines << lowerFillBasePoint(lines->first().x()); } } /*! \internal removes the two points from \a lines that were added by \ref addFillBasePoints. \see addFillBasePoints, lowerFillBasePoint, upperFillBasePoint */ void QCPGraph::removeFillBasePoints(QVector *lines) const { if (!lines) { qDebug() << Q_FUNC_INFO << "passed null as lineData"; return; } if (lines->isEmpty()) return; lines->remove(lines->size() - 2, 2); } /*! \internal called by \ref addFillBasePoints to conveniently assign the point which closes the fill polygon on the lower side of the zero-value-line parallel to the key axis. The logarithmic axis scale case is a bit special, since the zero-value-line in pixel coordinates is in positive or negative infinity. So this case is handled separately by just closing the fill polygon on the axis which lies in the direction towards the zero value. \a lowerKey will be the key (in pixels) of the returned point. Depending on whether the key axis is horizontal or vertical, \a lowerKey will end up as the x or y value of the returned point, respectively. \see upperFillBasePoint, addFillBasePoints */ QPointF QCPGraph::lowerFillBasePoint(double lowerKey) const { QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPointF(); } QPointF point; if (valueAxis->scaleType() == QCPAxis::stLinear) { if (keyAxis->axisType() == QCPAxis::atLeft) { point.setX(valueAxis->coordToPixel(0)); point.setY(lowerKey); } else if (keyAxis->axisType() == QCPAxis::atRight) { point.setX(valueAxis->coordToPixel(0)); point.setY(lowerKey); } else if (keyAxis->axisType() == QCPAxis::atTop) { point.setX(lowerKey); point.setY(valueAxis->coordToPixel(0)); } else if (keyAxis->axisType() == QCPAxis::atBottom) { point.setX(lowerKey); point.setY(valueAxis->coordToPixel(0)); } } else // valueAxis->mScaleType == QCPAxis::stLogarithmic { // In logarithmic scaling we can't just draw to value zero so we just fill all the way // to the axis which is in the direction towards zero if (keyAxis->orientation() == Qt::Vertical) { if ((valueAxis->range().upper < 0 && !valueAxis->rangeReversed()) || (valueAxis->range().upper > 0 && valueAxis->rangeReversed())) // if range is negative, zero is on opposite side of key axis point.setX(keyAxis->axisRect()->right()); else point.setX(keyAxis->axisRect()->left()); point.setY(lowerKey); } else if (keyAxis->axisType() == QCPAxis::atTop || keyAxis->axisType() == QCPAxis::atBottom) { point.setX(lowerKey); if ((valueAxis->range().upper < 0 && !valueAxis->rangeReversed()) || (valueAxis->range().upper > 0 && valueAxis->rangeReversed())) // if range is negative, zero is on opposite side of key axis point.setY(keyAxis->axisRect()->top()); else point.setY(keyAxis->axisRect()->bottom()); } } return point; } /*! \internal called by \ref addFillBasePoints to conveniently assign the point which closes the fill polygon on the upper side of the zero-value-line parallel to the key axis. The logarithmic axis scale case is a bit special, since the zero-value-line in pixel coordinates is in positive or negative infinity. So this case is handled separately by just closing the fill polygon on the axis which lies in the direction towards the zero value. \a upperKey will be the key (in pixels) of the returned point. Depending on whether the key axis is horizontal or vertical, \a upperKey will end up as the x or y value of the returned point, respectively. \see lowerFillBasePoint, addFillBasePoints */ QPointF QCPGraph::upperFillBasePoint(double upperKey) const { QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPointF(); } QPointF point; if (valueAxis->scaleType() == QCPAxis::stLinear) { if (keyAxis->axisType() == QCPAxis::atLeft) { point.setX(valueAxis->coordToPixel(0)); point.setY(upperKey); } else if (keyAxis->axisType() == QCPAxis::atRight) { point.setX(valueAxis->coordToPixel(0)); point.setY(upperKey); } else if (keyAxis->axisType() == QCPAxis::atTop) { point.setX(upperKey); point.setY(valueAxis->coordToPixel(0)); } else if (keyAxis->axisType() == QCPAxis::atBottom) { point.setX(upperKey); point.setY(valueAxis->coordToPixel(0)); } } else // valueAxis->mScaleType == QCPAxis::stLogarithmic { // In logarithmic scaling we can't just draw to value 0 so we just fill all the way // to the axis which is in the direction towards 0 if (keyAxis->orientation() == Qt::Vertical) { if ((valueAxis->range().upper < 0 && !valueAxis->rangeReversed()) || (valueAxis->range().upper > 0 && valueAxis->rangeReversed())) // if range is negative, zero is on opposite side of key axis point.setX(keyAxis->axisRect()->right()); else point.setX(keyAxis->axisRect()->left()); point.setY(upperKey); } else if (keyAxis->axisType() == QCPAxis::atTop || keyAxis->axisType() == QCPAxis::atBottom) { point.setX(upperKey); if ((valueAxis->range().upper < 0 && !valueAxis->rangeReversed()) || (valueAxis->range().upper > 0 && valueAxis->rangeReversed())) // if range is negative, zero is on opposite side of key axis point.setY(keyAxis->axisRect()->top()); else point.setY(keyAxis->axisRect()->bottom()); } } return point; } /*! \internal Generates the polygon needed for drawing channel fills between this graph and the graph specified in \a mChannelFillGraph (see \ref setChannelFillGraph). The data points representing the line of this graph in pixel coordinates must be passed in \a lines, the corresponding points of the other graph are generated by calling its \ref getLines method. This method may return an empty polygon if the key ranges of the two graphs have no overlap of if they don't have the same orientation (e.g. one key axis vertical, the other horizontal). For increased performance (due to implicit sharing), it is recommended to keep the returned QPolygonF const. */ const QPolygonF QCPGraph::getChannelFillPolygon(const QVector *lines) const { if (!mChannelFillGraph) return QPolygonF(); QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPolygonF(); } if (!mChannelFillGraph.data()->mKeyAxis) { qDebug() << Q_FUNC_INFO << "channel fill target key axis invalid"; return QPolygonF(); } if (mChannelFillGraph.data()->mKeyAxis.data()->orientation() != keyAxis->orientation()) return QPolygonF(); // don't have same axis orientation, can't fill that (Note: if keyAxis fits, valueAxis will fit too, because it's always orthogonal to keyAxis) if (lines->isEmpty()) return QPolygonF(); QVector otherData; mChannelFillGraph.data()->getLines(&otherData, QCPDataRange(0, mChannelFillGraph.data()->dataCount())); if (otherData.isEmpty()) return QPolygonF(); QVector thisData; thisData.reserve(lines->size() + otherData.size()); // because we will join both vectors at end of this function for ( int i = 0; i < lines->size(); ++i) // don't use the vector<<(vector), it squeezes internally, which ruins the performance tuning with reserve() thisData << lines->at(i); // pointers to be able to swap them, depending which data range needs cropping: QVector *staticData = &thisData; QVector *croppedData = &otherData; // crop both vectors to ranges in which the keys overlap (which coord is key, depends on axisType): if (keyAxis->orientation() == Qt::Horizontal) { // x is key // if an axis range is reversed, the data point keys will be descending. Reverse them, since following algorithm assumes ascending keys: if (staticData->first().x() > staticData->last().x()) { int size = staticData->size(); for (int i = 0; i < size / 2; ++i) qSwap((*staticData)[i], (*staticData)[size - 1 - i]); } if (croppedData->first().x() > croppedData->last().x()) { int size = croppedData->size(); for (int i = 0; i < size / 2; ++i) qSwap((*croppedData)[i], (*croppedData)[size - 1 - i]); } // crop lower bound: if (staticData->first().x() < croppedData->first().x()) // other one must be cropped qSwap(staticData, croppedData); int lowBound = findIndexBelowX(croppedData, staticData->first().x()); if (lowBound == -1) return QPolygonF(); // key ranges have no overlap croppedData->remove(0, lowBound); // set lowest point of cropped data to fit exactly key position of first static data // point via linear interpolation: if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation double slope; if (croppedData->at(1).x() - croppedData->at(0).x() != 0) slope = (croppedData->at(1).y() - croppedData->at(0).y()) / (croppedData->at(1).x() - croppedData->at(0).x()); else slope = 0; (*croppedData)[0].setY(croppedData->at(0).y() + slope * (staticData->first().x() - croppedData->at(0).x())); (*croppedData)[0].setX(staticData->first().x()); // crop upper bound: if (staticData->last().x() > croppedData->last().x()) // other one must be cropped qSwap(staticData, croppedData); int highBound = findIndexAboveX(croppedData, staticData->last().x()); if (highBound == -1) return QPolygonF(); // key ranges have no overlap croppedData->remove(highBound + 1, croppedData->size() - (highBound + 1)); // set highest point of cropped data to fit exactly key position of last static data // point via linear interpolation: if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation int li = croppedData->size() - 1; // last index if (croppedData->at(li).x() - croppedData->at(li - 1).x() != 0) slope = (croppedData->at(li).y() - croppedData->at(li - 1).y()) / (croppedData->at(li).x() - croppedData->at(li - 1).x()); else slope = 0; (*croppedData)[li].setY(croppedData->at(li - 1).y() + slope * (staticData->last().x() - croppedData->at(li - 1).x())); (*croppedData)[li].setX(staticData->last().x()); } else // mKeyAxis->orientation() == Qt::Vertical { // y is key // similar to "x is key" but switched x,y. Further, lower/upper meaning is inverted compared to x, // because in pixel coordinates, y increases from top to bottom, not bottom to top like data coordinate. // if an axis range is reversed, the data point keys will be descending. Reverse them, since following algorithm assumes ascending keys: if (staticData->first().y() < staticData->last().y()) { int size = staticData->size(); for (int i = 0; i < size / 2; ++i) qSwap((*staticData)[i], (*staticData)[size - 1 - i]); } if (croppedData->first().y() < croppedData->last().y()) { int size = croppedData->size(); for (int i = 0; i < size / 2; ++i) qSwap((*croppedData)[i], (*croppedData)[size - 1 - i]); } // crop lower bound: if (staticData->first().y() > croppedData->first().y()) // other one must be cropped qSwap(staticData, croppedData); int lowBound = findIndexAboveY(croppedData, staticData->first().y()); if (lowBound == -1) return QPolygonF(); // key ranges have no overlap croppedData->remove(0, lowBound); // set lowest point of cropped data to fit exactly key position of first static data // point via linear interpolation: if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation double slope; if (croppedData->at(1).y() - croppedData->at(0).y() != 0) // avoid division by zero in step plots slope = (croppedData->at(1).x() - croppedData->at(0).x()) / (croppedData->at(1).y() - croppedData->at(0).y()); else slope = 0; (*croppedData)[0].setX(croppedData->at(0).x() + slope * (staticData->first().y() - croppedData->at(0).y())); (*croppedData)[0].setY(staticData->first().y()); // crop upper bound: if (staticData->last().y() < croppedData->last().y()) // other one must be cropped qSwap(staticData, croppedData); int highBound = findIndexBelowY(croppedData, staticData->last().y()); if (highBound == -1) return QPolygonF(); // key ranges have no overlap croppedData->remove(highBound + 1, croppedData->size() - (highBound + 1)); // set highest point of cropped data to fit exactly key position of last static data // point via linear interpolation: if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation int li = croppedData->size() - 1; // last index if (croppedData->at(li).y() - croppedData->at(li - 1).y() != 0) // avoid division by zero in step plots slope = (croppedData->at(li).x() - croppedData->at(li - 1).x()) / (croppedData->at(li).y() - croppedData->at(li - 1).y()); else slope = 0; (*croppedData)[li].setX(croppedData->at(li - 1).x() + slope * (staticData->last().y() - croppedData->at(li - 1).y())); (*croppedData)[li].setY(staticData->last().y()); } // return joined: for (int i = otherData.size() - 1; i >= 0; --i) // insert reversed, otherwise the polygon will be twisted thisData << otherData.at(i); return QPolygonF(thisData); } /*! \internal Finds the smallest index of \a data, whose points x value is just above \a x. Assumes x values in \a data points are ordered ascending, as is the case when plotting with horizontal key axis. Used to calculate the channel fill polygon, see \ref getChannelFillPolygon. */ int QCPGraph::findIndexAboveX(const QVector *data, double x) const { for (int i = data->size() - 1; i >= 0; --i) { if (data->at(i).x() < x) { if (i < data->size() - 1) return i + 1; else return data->size() - 1; } } return -1; } /*! \internal Finds the highest index of \a data, whose points x value is just below \a x. Assumes x values in \a data points are ordered ascending, as is the case when plotting with horizontal key axis. Used to calculate the channel fill polygon, see \ref getChannelFillPolygon. */ int QCPGraph::findIndexBelowX(const QVector *data, double x) const { for (int i = 0; i < data->size(); ++i) { if (data->at(i).x() > x) { if (i > 0) return i - 1; else return 0; } } return -1; } /*! \internal Finds the smallest index of \a data, whose points y value is just above \a y. Assumes y values in \a data points are ordered descending, as is the case when plotting with vertical key axis. Used to calculate the channel fill polygon, see \ref getChannelFillPolygon. */ int QCPGraph::findIndexAboveY(const QVector *data, double y) const { for (int i = 0; i < data->size(); ++i) { if (data->at(i).y() < y) { if (i > 0) return i - 1; else return 0; } } return -1; } /*! \internal Calculates the minimum distance in pixels the graph's representation has from the given \a pixelPoint. This is used to determine whether the graph was clicked or not, e.g. in \ref selectTest. The closest data point to \a pixelPoint is returned in \a closestData. Note that if the graph has a line representation, the returned distance may be smaller than the distance to the \a closestData point, since the distance to the graph line is also taken into account. If either the graph has no data or if the line style is \ref lsNone and the scatter style's shape is \ref QCPScatterStyle::ssNone (i.e. there is no visual representation of the graph), returns -1.0. */ double QCPGraph::pointDistance(const QPointF &pixelPoint, QCPGraphDataContainer::const_iterator &closestData) const { closestData = mDataContainer->constEnd(); if (mDataContainer->isEmpty()) return -1.0; if (mLineStyle == lsNone && mScatterStyle.isNone()) return -1.0; // calculate minimum distances to graph data points and find closestData iterator: double minDistSqr = std::numeric_limits::max(); // determine which key range comes into question, taking selection tolerance around pos into account: double posKeyMin, posKeyMax, dummy; pixelsToCoords(pixelPoint - QPointF(mParentPlot->selectionTolerance(), mParentPlot->selectionTolerance()), posKeyMin, dummy); pixelsToCoords(pixelPoint + QPointF(mParentPlot->selectionTolerance(), mParentPlot->selectionTolerance()), posKeyMax, dummy); if (posKeyMin > posKeyMax) qSwap(posKeyMin, posKeyMax); // iterate over found data points and then choose the one with the shortest distance to pos: QCPGraphDataContainer::const_iterator begin = mDataContainer->findBegin(posKeyMin, true); QCPGraphDataContainer::const_iterator end = mDataContainer->findEnd(posKeyMax, true); for (QCPGraphDataContainer::const_iterator it = begin; it != end; ++it) { const double currentDistSqr = QCPVector2D(coordsToPixels(it->key, it->value) - pixelPoint).lengthSquared(); if (currentDistSqr < minDistSqr) { minDistSqr = currentDistSqr; closestData = it; } } // calculate distance to graph line if there is one (if so, will probably be smaller than distance to closest data point): if (mLineStyle != lsNone) { // line displayed, calculate distance to line segments: QVector lineData; getLines(&lineData, QCPDataRange(0, dataCount())); QCPVector2D p(pixelPoint); const int step = mLineStyle == lsImpulse ? 2 : 1; // impulse plot differs from other line styles in that the lineData points are only pairwise connected for (int i = 0; i < lineData.size() - 1; i += step) { const double currentDistSqr = p.distanceSquaredToLine(lineData.at(i), lineData.at(i + 1)); if (currentDistSqr < minDistSqr) minDistSqr = currentDistSqr; } } return qSqrt(minDistSqr); } /*! \internal Finds the highest index of \a data, whose points y value is just below \a y. Assumes y values in \a data points are ordered descending, as is the case when plotting with vertical key axis (since keys are ordered ascending). Used to calculate the channel fill polygon, see \ref getChannelFillPolygon. */ int QCPGraph::findIndexBelowY(const QVector *data, double y) const { for (int i = data->size() - 1; i >= 0; --i) { if (data->at(i).y() > y) { if (i < data->size() - 1) return i + 1; else return data->size() - 1; } } return -1; } /* end of 'src/plottables/plottable-graph.cpp' */ /* including file 'src/plottables/plottable-curve.cpp', size 60009 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPCurveData //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPCurveData \brief Holds the data of one single data point for QCPCurve. The stored data is: \li \a t: the free ordering parameter of this curve point, like in the mathematical vector (x(t), y(t)). (This is the \a sortKey) \li \a key: coordinate on the key axis of this curve point (this is the \a mainKey) \li \a value: coordinate on the value axis of this curve point (this is the \a mainValue) The container for storing multiple data points is \ref QCPCurveDataContainer. It is a typedef for \ref QCPDataContainer with \ref QCPCurveData as the DataType template parameter. See the documentation there for an explanation regarding the data type's generic methods. \see QCPCurveDataContainer */ /* start documentation of inline functions */ /*! \fn double QCPCurveData::sortKey() const Returns the \a t member of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn static QCPCurveData QCPCurveData::fromSortKey(double sortKey) Returns a data point with the specified \a sortKey (assigned to the data point's \a t member). All other members are set to zero. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn static static bool QCPCurveData::sortKeyIsMainKey() Since the member \a key is the data point key coordinate and the member \a t is the data ordering parameter, this method returns false. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn double QCPCurveData::mainKey() const Returns the \a key member of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn double QCPCurveData::mainValue() const Returns the \a value member of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn QCPRange QCPCurveData::valueRange() const Returns a QCPRange with both lower and upper boundary set to \a value of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /* end documentation of inline functions */ /*! Constructs a curve data point with t, key and value set to zero. */ QCPCurveData::QCPCurveData() : t(0), key(0), value(0) { } /*! Constructs a curve data point with the specified \a t, \a key and \a value. */ QCPCurveData::QCPCurveData(double t, double key, double value) : t(t), key(key), value(value) { } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPCurve //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPCurve \brief A plottable representing a parametric curve in a plot. \image html QCPCurve.png Unlike QCPGraph, plottables of this type may have multiple points with the same key coordinate, so their visual representation can have \a loops. This is realized by introducing a third coordinate \a t, which defines the order of the points described by the other two coordinates \a x and \a y. To plot data, assign it with the \ref setData or \ref addData functions. Alternatively, you can also access and modify the curve's data via the \ref data method, which returns a pointer to the internal \ref QCPCurveDataContainer. Gaps in the curve can be created by adding data points with NaN as key and value (qQNaN() or std::numeric_limits::quiet_NaN()) in between the two data points that shall be separated. \section qcpcurve-appearance Changing the appearance The appearance of the curve is determined by the pen and the brush (\ref setPen, \ref setBrush). \section qcpcurve-usage Usage Like all data representing objects in QCustomPlot, the QCPCurve is a plottable (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.) Usually, you first create an instance: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-creation-1 which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead. The newly created plottable can be modified, e.g.: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-creation-2 */ /* start of documentation of inline functions */ /*! \fn QSharedPointer QCPCurve::data() const Returns a shared pointer to the internal data storage of type \ref QCPCurveDataContainer. You may use it to directly manipulate the data, which may be more convenient and faster than using the regular \ref setData or \ref addData methods. */ /* end of documentation of inline functions */ /*! Constructs a curve which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have the same orientation. If either of these restrictions is violated, a corresponding message is printed to the debug output (qDebug), the construction is not aborted, though. The created QCPCurve is automatically registered with the QCustomPlot instance inferred from \a keyAxis. This QCustomPlot instance takes ownership of the QCPCurve, so do not delete it manually but use QCustomPlot::removePlottable() instead. */ QCPCurve::QCPCurve(QCPAxis *keyAxis, QCPAxis *valueAxis) : QCPAbstractPlottable1D(keyAxis, valueAxis) { // modify inherited properties from abstract plottable: setPen(QPen(Qt::blue, 0)); setBrush(Qt::NoBrush); setScatterStyle(QCPScatterStyle()); setLineStyle(lsLine); } QCPCurve::~QCPCurve() { } /*! \overload Replaces the current data container with the provided \a data container. Since a QSharedPointer is used, multiple QCPCurves may share the same data container safely. Modifying the data in the container will then affect all curves that share the container. Sharing can be achieved by simply exchanging the data containers wrapped in shared pointers: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-datasharing-1 If you do not wish to share containers, but create a copy from an existing container, rather use the \ref QCPDataContainer::set method on the curve's data container directly: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-datasharing-2 \see addData */ void QCPCurve::setData(QSharedPointer data) { mDataContainer = data; } /*! \overload Replaces the current data with the provided points in \a t, \a keys and \a values. The provided vectors should have equal length. Else, the number of added points will be the size of the smallest vector. If you can guarantee that the passed data points are sorted by \a t in ascending order, you can set \a alreadySorted to true, to improve performance by saving a sorting run. \see addData */ void QCPCurve::setData(const QVector &t, const QVector &keys, const QVector &values, bool alreadySorted) { mDataContainer->clear(); addData(t, keys, values, alreadySorted); } /*! \overload Replaces the current data with the provided points in \a keys and \a values. The provided vectors should have equal length. Else, the number of added points will be the size of the smallest vector. The t parameter of each data point will be set to the integer index of the respective key/value pair. \see addData */ void QCPCurve::setData(const QVector &keys, const QVector &values) { mDataContainer->clear(); addData(keys, values); } /*! Sets the visual appearance of single data points in the plot. If set to \ref QCPScatterStyle::ssNone, no scatter points are drawn (e.g. for line-only plots with appropriate line style). \see QCPScatterStyle, setLineStyle */ void QCPCurve::setScatterStyle(const QCPScatterStyle &style) { mScatterStyle = style; } /*! If scatters are displayed (scatter style not \ref QCPScatterStyle::ssNone), \a skip number of scatter points are skipped/not drawn after every drawn scatter point. This can be used to make the data appear sparser while for example still having a smooth line, and to improve performance for very high density plots. If \a skip is set to 0 (default), all scatter points are drawn. \see setScatterStyle */ void QCPCurve::setScatterSkip(int skip) { mScatterSkip = qMax(0, skip); } /*! Sets how the single data points are connected in the plot or how they are represented visually apart from the scatter symbol. For scatter-only plots, set \a style to \ref lsNone and \ref setScatterStyle to the desired scatter style. \see setScatterStyle */ void QCPCurve::setLineStyle(QCPCurve::LineStyle style) { mLineStyle = style; } /*! \overload Adds the provided points in \a t, \a keys and \a values to the current data. The provided vectors should have equal length. Else, the number of added points will be the size of the smallest vector. If you can guarantee that the passed data points are sorted by \a keys in ascending order, you can set \a alreadySorted to true, to improve performance by saving a sorting run. Alternatively, you can also access and modify the data directly via the \ref data method, which returns a pointer to the internal data container. */ void QCPCurve::addData(const QVector &t, const QVector &keys, const QVector &values, bool alreadySorted) { if (t.size() != keys.size() || t.size() != values.size()) qDebug() << Q_FUNC_INFO << "ts, keys and values have different sizes:" << t.size() << keys.size() << values.size(); const int n = qMin(qMin(t.size(), keys.size()), values.size()); QVector tempData(n); QVector::iterator it = tempData.begin(); const QVector::iterator itEnd = tempData.end(); int i = 0; while (it != itEnd) { it->t = t[i]; it->key = keys[i]; it->value = values[i]; ++it; ++i; } mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write } /*! \overload Adds the provided points in \a keys and \a values to the current data. The provided vectors should have equal length. Else, the number of added points will be the size of the smallest vector. The t parameter of each data point will be set to the integer index of the respective key/value pair. Alternatively, you can also access and modify the data directly via the \ref data method, which returns a pointer to the internal data container. */ void QCPCurve::addData(const QVector &keys, const QVector &values) { if (keys.size() != values.size()) qDebug() << Q_FUNC_INFO << "keys and values have different sizes:" << keys.size() << values.size(); const int n = qMin(keys.size(), values.size()); double tStart; if (!mDataContainer->isEmpty()) tStart = (mDataContainer->constEnd() - 1)->t + 1.0; else tStart = 0; QVector tempData(n); QVector::iterator it = tempData.begin(); const QVector::iterator itEnd = tempData.end(); int i = 0; while (it != itEnd) { it->t = tStart + i; it->key = keys[i]; it->value = values[i]; ++it; ++i; } mDataContainer->add(tempData, true); // don't modify tempData beyond this to prevent copy on write } /*! \overload Adds the provided data point as \a t, \a key and \a value to the current data. Alternatively, you can also access and modify the data directly via the \ref data method, which returns a pointer to the internal data container. */ void QCPCurve::addData(double t, double key, double value) { mDataContainer->add(QCPCurveData(t, key, value)); } /*! \overload Adds the provided data point as \a key and \a value to the current data. The t parameter is generated automatically by increments of 1 for each point, starting at the highest t of previously existing data or 0, if the curve data is empty. Alternatively, you can also access and modify the data directly via the \ref data method, which returns a pointer to the internal data container. */ void QCPCurve::addData(double key, double value) { if (!mDataContainer->isEmpty()) mDataContainer->add(QCPCurveData((mDataContainer->constEnd() - 1)->t + 1.0, key, value)); else mDataContainer->add(QCPCurveData(0.0, key, value)); } /* inherits documentation from base class */ double QCPCurve::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty()) return -1; if (!mKeyAxis || !mValueAxis) return -1; if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint())) { QCPCurveDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd(); double result = pointDistance(pos, closestDataPoint); if (details) { int pointIndex = closestDataPoint - mDataContainer->constBegin(); details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex + 1))); } return result; } else return -1; } /* inherits documentation from base class */ QCPRange QCPCurve::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const { return mDataContainer->keyRange(foundRange, inSignDomain); } /* inherits documentation from base class */ QCPRange QCPCurve::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const { return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange); } /* inherits documentation from base class */ void QCPCurve::draw(QCPPainter *painter) { if (mDataContainer->isEmpty()) return; // allocate line vector: QVector lines, scatters; // loop over and draw segments of unselected/selected data: QList selectedSegments, unselectedSegments, allSegments; getDataSegments(selectedSegments, unselectedSegments); allSegments << unselectedSegments << selectedSegments; for (int i = 0; i < allSegments.size(); ++i) { bool isSelectedSegment = i >= unselectedSegments.size(); // fill with curve data: QPen finalCurvePen = mPen; // determine the final pen already here, because the line optimization depends on its stroke width if (isSelectedSegment && mSelectionDecorator) finalCurvePen = mSelectionDecorator->pen(); QCPDataRange lineDataRange = isSelectedSegment ? allSegments.at(i) : allSegments.at(i).adjusted( -1, 1); // unselected segments extend lines to bordering selected data point (safe to exceed total data bounds in first/last segment, getCurveLines takes care) getCurveLines(&lines, lineDataRange, finalCurvePen.widthF()); // check data validity if flag set: #ifdef QCUSTOMPLOT_CHECK_DATA for (QCPCurveDataContainer::const_iterator it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it) { if (QCP::isInvalidData(it->t) || QCP::isInvalidData(it->key, it->value)) qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "invalid." << "Plottable name:" << name(); } #endif // draw curve fill: applyFillAntialiasingHint(painter); if (isSelectedSegment && mSelectionDecorator) mSelectionDecorator->applyBrush(painter); else painter->setBrush(mBrush); painter->setPen(Qt::NoPen); if (painter->brush().style() != Qt::NoBrush && painter->brush().color().alpha() != 0) painter->drawPolygon(QPolygonF(lines)); // draw curve line: if (mLineStyle != lsNone) { painter->setPen(finalCurvePen); painter->setBrush(Qt::NoBrush); drawCurveLine(painter, lines); } // draw scatters: QCPScatterStyle finalScatterStyle = mScatterStyle; if (isSelectedSegment && mSelectionDecorator) finalScatterStyle = mSelectionDecorator->getFinalScatterStyle(mScatterStyle); if (!finalScatterStyle.isNone()) { getScatters(&scatters, allSegments.at(i), finalScatterStyle.size()); drawScatterPlot(painter, scatters, finalScatterStyle); } } // draw other selection decoration that isn't just line/scatter pens and brushes: if (mSelectionDecorator) mSelectionDecorator->drawDecoration(painter, selection()); } /* inherits documentation from base class */ void QCPCurve::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const { // draw fill: if (mBrush.style() != Qt::NoBrush) { applyFillAntialiasingHint(painter); painter->fillRect(QRectF(rect.left(), rect.top() + rect.height() / 2.0, rect.width(), rect.height() / 3.0), mBrush); } // draw line vertically centered: if (mLineStyle != lsNone) { applyDefaultAntialiasingHint(painter); painter->setPen(mPen); painter->drawLine( QLineF(rect.left(), rect.top() + rect.height() / 2.0, rect.right() + 5, rect.top() + rect.height() / 2.0)); // +5 on x2 else last segment is missing from dashed/dotted pens } // draw scatter symbol: if (!mScatterStyle.isNone()) { applyScattersAntialiasingHint(painter); // scale scatter pixmap if it's too large to fit in legend icon rect: if (mScatterStyle.shape() == QCPScatterStyle::ssPixmap && (mScatterStyle.pixmap().size().width() > rect.width() || mScatterStyle.pixmap().size().height() > rect.height())) { QCPScatterStyle scaledStyle(mScatterStyle); scaledStyle.setPixmap( scaledStyle.pixmap().scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); scaledStyle.applyTo(painter, mPen); scaledStyle.drawShape(painter, QRectF(rect).center()); } else { mScatterStyle.applyTo(painter, mPen); mScatterStyle.drawShape(painter, QRectF(rect).center()); } } } /*! \internal Draws lines between the points in \a lines, given in pixel coordinates. \see drawScatterPlot, getCurveLines */ void QCPCurve::drawCurveLine(QCPPainter *painter, const QVector &lines) const { if (painter->pen().style() != Qt::NoPen && painter->pen().color().alpha() != 0) { applyDefaultAntialiasingHint(painter); drawPolyline(painter, lines); } } /*! \internal Draws scatter symbols at every point passed in \a points, given in pixel coordinates. The scatters will be drawn with \a painter and have the appearance as specified in \a style. \see drawCurveLine, getCurveLines */ void QCPCurve::drawScatterPlot(QCPPainter *painter, const QVector &points, const QCPScatterStyle &style) const { // draw scatter point symbols: applyScattersAntialiasingHint(painter); style.applyTo(painter, mPen); for (int i = 0; i < points.size(); ++i) if (!qIsNaN(points.at(i).x()) && !qIsNaN(points.at(i).y())) style.drawShape(painter, points.at(i)); } /*! \internal Called by \ref draw to generate points in pixel coordinates which represent the line of the curve. Line segments that aren't visible in the current axis rect are handled in an optimized way. They are projected onto a rectangle slightly larger than the visible axis rect and simplified regarding point count. The algorithm makes sure to preserve appearance of lines and fills inside the visible axis rect by generating new temporary points on the outer rect if necessary. \a lines will be filled with points in pixel coordinates, that can be drawn with \ref drawCurveLine. \a dataRange specifies the beginning and ending data indices that will be taken into account for conversion. In this function, the specified range may exceed the total data bounds without harm: a correspondingly trimmed data range will be used. This takes the burden off the user of this function to check for valid indices in \a dataRange, e.g. when extending ranges coming from \ref getDataSegments. \a penWidth specifies the pen width that will be used to later draw the lines generated by this function. This is needed here to calculate an accordingly wider margin around the axis rect when performing the line optimization. Methods that are also involved in the algorithm are: \ref getRegion, \ref getOptimizedPoint, \ref getOptimizedCornerPoints \ref mayTraverse, \ref getTraverse, \ref getTraverseCornerPoints. \see drawCurveLine, drawScatterPlot */ void QCPCurve::getCurveLines(QVector *lines, const QCPDataRange &dataRange, double penWidth) const { if (!lines) return; lines->clear(); QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; } // add margins to rect to compensate for stroke width const double strokeMargin = qMax(qreal(1.0), qreal(penWidth * 0.75)); // stroke radius + 50% safety const double keyMin = keyAxis->pixelToCoord(keyAxis->coordToPixel(keyAxis->range().lower) - strokeMargin * keyAxis->pixelOrientation()); const double keyMax = keyAxis->pixelToCoord(keyAxis->coordToPixel(keyAxis->range().upper) + strokeMargin * keyAxis->pixelOrientation()); const double valueMin = valueAxis->pixelToCoord(valueAxis->coordToPixel(valueAxis->range().lower) - strokeMargin * valueAxis->pixelOrientation()); const double valueMax = valueAxis->pixelToCoord(valueAxis->coordToPixel(valueAxis->range().upper) + strokeMargin * valueAxis->pixelOrientation()); QCPCurveDataContainer::const_iterator itBegin = mDataContainer->constBegin(); QCPCurveDataContainer::const_iterator itEnd = mDataContainer->constEnd(); mDataContainer->limitIteratorsToDataRange(itBegin, itEnd, dataRange); if (itBegin == itEnd) return; QCPCurveDataContainer::const_iterator it = itBegin; QCPCurveDataContainer::const_iterator prevIt = itEnd - 1; int prevRegion = getRegion(prevIt->key, prevIt->value, keyMin, valueMax, keyMax, valueMin); QVector trailingPoints; // points that must be applied after all other points (are generated only when handling first point to get virtual segment between last and first point right) while (it != itEnd) { const int currentRegion = getRegion(it->key, it->value, keyMin, valueMax, keyMax, valueMin); if (currentRegion != prevRegion) // changed region, possibly need to add some optimized edge points or original points if entering R { if (currentRegion != 5) // segment doesn't end in R, so it's a candidate for removal { QPointF crossA, crossB; if (prevRegion == 5) // we're coming from R, so add this point optimized { lines->append(getOptimizedPoint(currentRegion, it->key, it->value, prevIt->key, prevIt->value, keyMin, valueMax, keyMax, valueMin)); // in the situations 5->1/7/9/3 the segment may leave R and directly cross through two outer regions. In these cases we need to add an additional corner point *lines << getOptimizedCornerPoints(prevRegion, currentRegion, prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin); } else if (mayTraverse(prevRegion, currentRegion) && getTraverse(prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin, crossA, crossB)) { // add the two cross points optimized if segment crosses R and if segment isn't virtual zeroth segment between last and first curve point: QVector beforeTraverseCornerPoints, afterTraverseCornerPoints; getTraverseCornerPoints(prevRegion, currentRegion, keyMin, valueMax, keyMax, valueMin, beforeTraverseCornerPoints, afterTraverseCornerPoints); if (it != itBegin) { *lines << beforeTraverseCornerPoints; lines->append(crossA); lines->append(crossB); *lines << afterTraverseCornerPoints; } else { lines->append(crossB); *lines << afterTraverseCornerPoints; trailingPoints << beforeTraverseCornerPoints << crossA; } } else // doesn't cross R, line is just moving around in outside regions, so only need to add optimized point(s) at the boundary corner(s) { *lines << getOptimizedCornerPoints(prevRegion, currentRegion, prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin); } } else // segment does end in R, so we add previous point optimized and this point at original position { if (it == itBegin) // it is first point in curve and prevIt is last one. So save optimized point for adding it to the lineData in the end trailingPoints << getOptimizedPoint(prevRegion, prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin); else lines->append(getOptimizedPoint(prevRegion, prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin)); lines->append(coordsToPixels(it->key, it->value)); } } else // region didn't change { if (currentRegion == 5) // still in R, keep adding original points { lines->append(coordsToPixels(it->key, it->value)); } else // still outside R, no need to add anything { // see how this is not doing anything? That's the main optimization... } } prevIt = it; prevRegion = currentRegion; ++it; } *lines << trailingPoints; } /*! \internal Called by \ref draw to generate points in pixel coordinates which represent the scatters of the curve. If a scatter skip is configured (\ref setScatterSkip), the returned points are accordingly sparser. Scatters that aren't visible in the current axis rect are optimized away. \a scatters will be filled with points in pixel coordinates, that can be drawn with \ref drawScatterPlot. \a dataRange specifies the beginning and ending data indices that will be taken into account for conversion. \a scatterWidth specifies the scatter width that will be used to later draw the scatters at pixel coordinates generated by this function. This is needed here to calculate an accordingly wider margin around the axis rect when performing the data point reduction. \see draw, drawScatterPlot */ void QCPCurve::getScatters(QVector *scatters, const QCPDataRange &dataRange, double scatterWidth) const { if (!scatters) return; scatters->clear(); QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; } QCPCurveDataContainer::const_iterator begin = mDataContainer->constBegin(); QCPCurveDataContainer::const_iterator end = mDataContainer->constEnd(); mDataContainer->limitIteratorsToDataRange(begin, end, dataRange); if (begin == end) return; const int scatterModulo = mScatterSkip + 1; const bool doScatterSkip = mScatterSkip > 0; int endIndex = end - mDataContainer->constBegin(); QCPRange keyRange = keyAxis->range(); QCPRange valueRange = valueAxis->range(); // extend range to include width of scatter symbols: keyRange.lower = keyAxis->pixelToCoord(keyAxis->coordToPixel(keyRange.lower) - scatterWidth * keyAxis->pixelOrientation()); keyRange.upper = keyAxis->pixelToCoord(keyAxis->coordToPixel(keyRange.upper) + scatterWidth * keyAxis->pixelOrientation()); valueRange.lower = valueAxis->pixelToCoord(valueAxis->coordToPixel(valueRange.lower) - scatterWidth * valueAxis->pixelOrientation()); valueRange.upper = valueAxis->pixelToCoord(valueAxis->coordToPixel(valueRange.upper) + scatterWidth * valueAxis->pixelOrientation()); QCPCurveDataContainer::const_iterator it = begin; int itIndex = begin - mDataContainer->constBegin(); while (doScatterSkip && it != end && itIndex % scatterModulo != 0) // advance begin iterator to first non-skipped scatter { ++itIndex; ++it; } if (keyAxis->orientation() == Qt::Vertical) { while (it != end) { if (!qIsNaN(it->value) && keyRange.contains(it->key) && valueRange.contains(it->value)) scatters->append(QPointF(valueAxis->coordToPixel(it->value), keyAxis->coordToPixel(it->key))); // advance iterator to next (non-skipped) data point: if (!doScatterSkip) ++it; else { itIndex += scatterModulo; if (itIndex < endIndex) // make sure we didn't jump over end it += scatterModulo; else { it = end; itIndex = endIndex; } } } } else { while (it != end) { if (!qIsNaN(it->value) && keyRange.contains(it->key) && valueRange.contains(it->value)) scatters->append(QPointF(keyAxis->coordToPixel(it->key), valueAxis->coordToPixel(it->value))); // advance iterator to next (non-skipped) data point: if (!doScatterSkip) ++it; else { itIndex += scatterModulo; if (itIndex < endIndex) // make sure we didn't jump over end it += scatterModulo; else { it = end; itIndex = endIndex; } } } } } /*! \internal This function is part of the curve optimization algorithm of \ref getCurveLines. It returns the region of the given point (\a key, \a value) with respect to a rectangle defined by \a keyMin, \a keyMax, \a valueMin, and \a valueMax. The regions are enumerated from top to bottom (\a valueMin to \a valueMax) and left to right (\a keyMin to \a keyMax):
147
258
369
With the rectangle being region 5, and the outer regions extending infinitely outwards. In the curve optimization algorithm, region 5 is considered to be the visible portion of the plot. */ int QCPCurve::getRegion(double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const { if (key < keyMin) // region 123 { if (value > valueMax) return 1; else if (value < valueMin) return 3; else return 2; } else if (key > keyMax) // region 789 { if (value > valueMax) return 7; else if (value < valueMin) return 9; else return 8; } else // region 456 { if (value > valueMax) return 4; else if (value < valueMin) return 6; else return 5; } } /*! \internal This function is part of the curve optimization algorithm of \ref getCurveLines. This method is used in case the current segment passes from inside the visible rect (region 5, see \ref getRegion) to any of the outer regions (\a otherRegion). The current segment is given by the line connecting (\a key, \a value) with (\a otherKey, \a otherValue). It returns the intersection point of the segment with the border of region 5. For this function it doesn't matter whether (\a key, \a value) is the point inside region 5 or whether it's (\a otherKey, \a otherValue), i.e. whether the segment is coming from region 5 or leaving it. It is important though that \a otherRegion correctly identifies the other region not equal to 5. */ QPointF QCPCurve::getOptimizedPoint(int otherRegion, double otherKey, double otherValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const { double intersectKey = keyMin; // initial value is just fail-safe double intersectValue = valueMax; // initial value is just fail-safe switch (otherRegion) { case 1: // top and left edge { intersectValue = valueMax; intersectKey = otherKey + (key - otherKey) / (value - otherValue) * (intersectValue - otherValue); if (intersectKey < keyMin || intersectKey > keyMax) // doesn't intersect, so must intersect other: { intersectKey = keyMin; intersectValue = otherValue + (value - otherValue) / (key - otherKey) * (intersectKey - otherKey); } break; } case 2: // left edge { intersectKey = keyMin; intersectValue = otherValue + (value - otherValue) / (key - otherKey) * (intersectKey - otherKey); break; } case 3: // bottom and left edge { intersectValue = valueMin; intersectKey = otherKey + (key - otherKey) / (value - otherValue) * (intersectValue - otherValue); if (intersectKey < keyMin || intersectKey > keyMax) // doesn't intersect, so must intersect other: { intersectKey = keyMin; intersectValue = otherValue + (value - otherValue) / (key - otherKey) * (intersectKey - otherKey); } break; } case 4: // top edge { intersectValue = valueMax; intersectKey = otherKey + (key - otherKey) / (value - otherValue) * (intersectValue - otherValue); break; } case 5: { break; // case 5 shouldn't happen for this function but we add it anyway to prevent potential discontinuity in branch table } case 6: // bottom edge { intersectValue = valueMin; intersectKey = otherKey + (key - otherKey) / (value - otherValue) * (intersectValue - otherValue); break; } case 7: // top and right edge { intersectValue = valueMax; intersectKey = otherKey + (key - otherKey) / (value - otherValue) * (intersectValue - otherValue); if (intersectKey < keyMin || intersectKey > keyMax) // doesn't intersect, so must intersect other: { intersectKey = keyMax; intersectValue = otherValue + (value - otherValue) / (key - otherKey) * (intersectKey - otherKey); } break; } case 8: // right edge { intersectKey = keyMax; intersectValue = otherValue + (value - otherValue) / (key - otherKey) * (intersectKey - otherKey); break; } case 9: // bottom and right edge { intersectValue = valueMin; intersectKey = otherKey + (key - otherKey) / (value - otherValue) * (intersectValue - otherValue); if (intersectKey < keyMin || intersectKey > keyMax) // doesn't intersect, so must intersect other: { intersectKey = keyMax; intersectValue = otherValue + (value - otherValue) / (key - otherKey) * (intersectKey - otherKey); } break; } } return coordsToPixels(intersectKey, intersectValue); } /*! \internal This function is part of the curve optimization algorithm of \ref getCurveLines. In situations where a single segment skips over multiple regions it might become necessary to add extra points at the corners of region 5 (see \ref getRegion) such that the optimized segment doesn't unintentionally cut through the visible area of the axis rect and create plot artifacts. This method provides these points that must be added, assuming the original segment doesn't start, end, or traverse region 5. (Corner points where region 5 is traversed are calculated by \ref getTraverseCornerPoints.) For example, consider a segment which directly goes from region 4 to 2 but originally is far out to the top left such that it doesn't cross region 5. Naively optimizing these points by projecting them on the top and left borders of region 5 will create a segment that surely crosses 5, creating a visual artifact in the plot. This method prevents this by providing extra points at the top left corner, making the optimized curve correctly pass from region 4 to 1 to 2 without traversing 5. */ QVector QCPCurve::getOptimizedCornerPoints(int prevRegion, int currentRegion, double prevKey, double prevValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const { QVector result; switch (prevRegion) { case 1: { switch (currentRegion) { case 2: { result << coordsToPixels(keyMin, valueMax); break; } case 4: { result << coordsToPixels(keyMin, valueMax); break; } case 3: { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMin, valueMin); break; } case 7: { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMax, valueMax); break; } case 6: { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMin, valueMin); result.append(result.last()); break; } case 8: { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMax, valueMax); result.append(result.last()); break; } case 9: // in this case we need another distinction of cases: segment may pass below or above rect, requiring either bottom right or top left corner points { if ((value - prevValue) / (key - prevKey) * (keyMin - key) + value < valueMin) // segment passes below R { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMin, valueMin); result.append(result.last()); result << coordsToPixels(keyMax, valueMin); } else { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMax, valueMax); result.append(result.last()); result << coordsToPixels(keyMax, valueMin); } break; } } break; } case 2: { switch (currentRegion) { case 1: { result << coordsToPixels(keyMin, valueMax); break; } case 3: { result << coordsToPixels(keyMin, valueMin); break; } case 4: { result << coordsToPixels(keyMin, valueMax); result.append(result.last()); break; } case 6: { result << coordsToPixels(keyMin, valueMin); result.append(result.last()); break; } case 7: { result << coordsToPixels(keyMin, valueMax); result.append(result.last()); result << coordsToPixels(keyMax, valueMax); break; } case 9: { result << coordsToPixels(keyMin, valueMin); result.append(result.last()); result << coordsToPixels(keyMax, valueMin); break; } } break; } case 3: { switch (currentRegion) { case 2: { result << coordsToPixels(keyMin, valueMin); break; } case 6: { result << coordsToPixels(keyMin, valueMin); break; } case 1: { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMin, valueMax); break; } case 9: { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMax, valueMin); break; } case 4: { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMin, valueMax); result.append(result.last()); break; } case 8: { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMax, valueMin); result.append(result.last()); break; } case 7: // in this case we need another distinction of cases: segment may pass below or above rect, requiring either bottom right or top left corner points { if ((value - prevValue) / (key - prevKey) * (keyMax - key) + value < valueMin) // segment passes below R { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMax, valueMin); result.append(result.last()); result << coordsToPixels(keyMax, valueMax); } else { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMin, valueMax); result.append(result.last()); result << coordsToPixels(keyMax, valueMax); } break; } } break; } case 4: { switch (currentRegion) { case 1: { result << coordsToPixels(keyMin, valueMax); break; } case 7: { result << coordsToPixels(keyMax, valueMax); break; } case 2: { result << coordsToPixels(keyMin, valueMax); result.append(result.last()); break; } case 8: { result << coordsToPixels(keyMax, valueMax); result.append(result.last()); break; } case 3: { result << coordsToPixels(keyMin, valueMax); result.append(result.last()); result << coordsToPixels(keyMin, valueMin); break; } case 9: { result << coordsToPixels(keyMax, valueMax); result.append(result.last()); result << coordsToPixels(keyMax, valueMin); break; } } break; } case 5: { switch (currentRegion) { case 1: { result << coordsToPixels(keyMin, valueMax); break; } case 7: { result << coordsToPixels(keyMax, valueMax); break; } case 9: { result << coordsToPixels(keyMax, valueMin); break; } case 3: { result << coordsToPixels(keyMin, valueMin); break; } } break; } case 6: { switch (currentRegion) { case 3: { result << coordsToPixels(keyMin, valueMin); break; } case 9: { result << coordsToPixels(keyMax, valueMin); break; } case 2: { result << coordsToPixels(keyMin, valueMin); result.append(result.last()); break; } case 8: { result << coordsToPixels(keyMax, valueMin); result.append(result.last()); break; } case 1: { result << coordsToPixels(keyMin, valueMin); result.append(result.last()); result << coordsToPixels(keyMin, valueMax); break; } case 7: { result << coordsToPixels(keyMax, valueMin); result.append(result.last()); result << coordsToPixels(keyMax, valueMax); break; } } break; } case 7: { switch (currentRegion) { case 4: { result << coordsToPixels(keyMax, valueMax); break; } case 8: { result << coordsToPixels(keyMax, valueMax); break; } case 1: { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMin, valueMax); break; } case 9: { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMax, valueMin); break; } case 2: { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMin, valueMax); result.append(result.last()); break; } case 6: { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMax, valueMin); result.append(result.last()); break; } case 3: // in this case we need another distinction of cases: segment may pass below or above rect, requiring either bottom right or top left corner points { if ((value - prevValue) / (key - prevKey) * (keyMax - key) + value < valueMin) // segment passes below R { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMax, valueMin); result.append(result.last()); result << coordsToPixels(keyMin, valueMin); } else { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMin, valueMax); result.append(result.last()); result << coordsToPixels(keyMin, valueMin); } break; } } break; } case 8: { switch (currentRegion) { case 7: { result << coordsToPixels(keyMax, valueMax); break; } case 9: { result << coordsToPixels(keyMax, valueMin); break; } case 4: { result << coordsToPixels(keyMax, valueMax); result.append(result.last()); break; } case 6: { result << coordsToPixels(keyMax, valueMin); result.append(result.last()); break; } case 1: { result << coordsToPixels(keyMax, valueMax); result.append(result.last()); result << coordsToPixels(keyMin, valueMax); break; } case 3: { result << coordsToPixels(keyMax, valueMin); result.append(result.last()); result << coordsToPixels(keyMin, valueMin); break; } } break; } case 9: { switch (currentRegion) { case 6: { result << coordsToPixels(keyMax, valueMin); break; } case 8: { result << coordsToPixels(keyMax, valueMin); break; } case 3: { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMin, valueMin); break; } case 7: { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMax, valueMax); break; } case 2: { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMin, valueMin); result.append(result.last()); break; } case 4: { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMax, valueMax); result.append(result.last()); break; } case 1: // in this case we need another distinction of cases: segment may pass below or above rect, requiring either bottom right or top left corner points { if ((value - prevValue) / (key - prevKey) * (keyMin - key) + value < valueMin) // segment passes below R { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMin, valueMin); result.append(result.last()); result << coordsToPixels(keyMin, valueMax); } else { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMax, valueMax); result.append(result.last()); result << coordsToPixels(keyMin, valueMax); } break; } } break; } } return result; } /*! \internal This function is part of the curve optimization algorithm of \ref getCurveLines. This method returns whether a segment going from \a prevRegion to \a currentRegion (see \ref getRegion) may traverse the visible region 5. This function assumes that neither \a prevRegion nor \a currentRegion is 5 itself. If this method returns false, the segment for sure doesn't pass region 5. If it returns true, the segment may or may not pass region 5 and a more fine-grained calculation must be used (\ref getTraverse). */ bool QCPCurve::mayTraverse(int prevRegion, int currentRegion) const { switch (prevRegion) { case 1: { switch (currentRegion) { case 4: case 7: case 2: case 3: return false; default: return true; } } case 2: { switch (currentRegion) { case 1: case 3: return false; default: return true; } } case 3: { switch (currentRegion) { case 1: case 2: case 6: case 9: return false; default: return true; } } case 4: { switch (currentRegion) { case 1: case 7: return false; default: return true; } } case 5: return false; // should never occur case 6: { switch (currentRegion) { case 3: case 9: return false; default: return true; } } case 7: { switch (currentRegion) { case 1: case 4: case 8: case 9: return false; default: return true; } } case 8: { switch (currentRegion) { case 7: case 9: return false; default: return true; } } case 9: { switch (currentRegion) { case 3: case 6: case 8: case 7: return false; default: return true; } } default: return true; } } /*! \internal This function is part of the curve optimization algorithm of \ref getCurveLines. This method assumes that the \ref mayTraverse test has returned true, so there is a chance the segment defined by (\a prevKey, \a prevValue) and (\a key, \a value) goes through the visible region 5. The return value of this method indicates whether the segment actually traverses region 5 or not. If the segment traverses 5, the output parameters \a crossA and \a crossB indicate the entry and exit points of region 5. They will become the optimized points for that segment. */ bool QCPCurve::getTraverse(double prevKey, double prevValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin, QPointF &crossA, QPointF &crossB) const { QList intersections; // x of QPointF corresponds to key and y to value if (qFuzzyIsNull(key - prevKey)) // line is parallel to value axis { // due to region filter in mayTraverseR(), if line is parallel to value or key axis, R is traversed here intersections.append(QPointF(key, valueMin)); // direction will be taken care of at end of method intersections.append(QPointF(key, valueMax)); } else if (qFuzzyIsNull(value - prevValue)) // line is parallel to key axis { // due to region filter in mayTraverseR(), if line is parallel to value or key axis, R is traversed here intersections.append(QPointF(keyMin, value)); // direction will be taken care of at end of method intersections.append(QPointF(keyMax, value)); } else // line is skewed { double gamma; double keyPerValue = (key - prevKey) / (value - prevValue); // check top of rect: gamma = prevKey + (valueMax - prevValue) * keyPerValue; if (gamma >= keyMin && gamma <= keyMax) intersections.append(QPointF(gamma, valueMax)); // check bottom of rect: gamma = prevKey + (valueMin - prevValue) * keyPerValue; if (gamma >= keyMin && gamma <= keyMax) intersections.append(QPointF(gamma, valueMin)); double valuePerKey = 1.0 / keyPerValue; // check left of rect: gamma = prevValue + (keyMin - prevKey) * valuePerKey; if (gamma >= valueMin && gamma <= valueMax) intersections.append(QPointF(keyMin, gamma)); // check right of rect: gamma = prevValue + (keyMax - prevKey) * valuePerKey; if (gamma >= valueMin && gamma <= valueMax) intersections.append(QPointF(keyMax, gamma)); } // handle cases where found points isn't exactly 2: if (intersections.size() > 2) { // line probably goes through corner of rect, and we got duplicate points there. single out the point pair with greatest distance in between: double distSqrMax = 0; QPointF pv1, pv2; for (int i = 0; i < intersections.size() - 1; ++i) { for (int k = i + 1; k < intersections.size(); ++k) { QPointF distPoint = intersections.at(i) - intersections.at(k); double distSqr = distPoint.x() * distPoint.x() + distPoint.y() + distPoint.y(); if (distSqr > distSqrMax) { pv1 = intersections.at(i); pv2 = intersections.at(k); distSqrMax = distSqr; } } } intersections = QList() << pv1 << pv2; } else if (intersections.size() != 2) { // one or even zero points found (shouldn't happen unless line perfectly tangent to corner), no need to draw segment return false; } // possibly re-sort points so optimized point segment has same direction as original segment: if ((key - prevKey) * (intersections.at(1).x() - intersections.at(0).x()) + (value - prevValue) * (intersections.at(1).y() - intersections.at(0).y()) < 0) // scalar product of both segments < 0 -> opposite direction intersections.move(0, 1); crossA = coordsToPixels(intersections.at(0).x(), intersections.at(0).y()); crossB = coordsToPixels(intersections.at(1).x(), intersections.at(1).y()); return true; } /*! \internal This function is part of the curve optimization algorithm of \ref getCurveLines. This method assumes that the \ref getTraverse test has returned true, so the segment definitely traverses the visible region 5 when going from \a prevRegion to \a currentRegion. In certain situations it is not sufficient to merely generate the entry and exit points of the segment into/out of region 5, as \ref getTraverse provides. It may happen that a single segment, in addition to traversing region 5, skips another region outside of region 5, which makes it necessary to add an optimized corner point there (very similar to the job \ref getOptimizedCornerPoints does for segments that are completely in outside regions and don't traverse 5). As an example, consider a segment going from region 1 to region 6, traversing the lower left corner of region 5. In this configuration, the segment additionally crosses the border between region 1 and 2 before entering region 5. This makes it necessary to add an additional point in the top left corner, before adding the optimized traverse points. So in this case, the output parameter \a beforeTraverse will contain the top left corner point, and \a afterTraverse will be empty. In some cases, such as when going from region 1 to 9, it may even be necessary to add additional corner points before and after the traverse. Then both \a beforeTraverse and \a afterTraverse return the respective corner points. */ void QCPCurve::getTraverseCornerPoints(int prevRegion, int currentRegion, double keyMin, double valueMax, double keyMax, double valueMin, QVector &beforeTraverse, QVector &afterTraverse) const { switch (prevRegion) { case 1: { switch (currentRegion) { case 6: { beforeTraverse << coordsToPixels(keyMin, valueMax); break; } case 9: { beforeTraverse << coordsToPixels(keyMin, valueMax); afterTraverse << coordsToPixels(keyMax, valueMin); break; } case 8: { beforeTraverse << coordsToPixels(keyMin, valueMax); break; } } break; } case 2: { switch (currentRegion) { case 7: { afterTraverse << coordsToPixels(keyMax, valueMax); break; } case 9: { afterTraverse << coordsToPixels(keyMax, valueMin); break; } } break; } case 3: { switch (currentRegion) { case 4: { beforeTraverse << coordsToPixels(keyMin, valueMin); break; } case 7: { beforeTraverse << coordsToPixels(keyMin, valueMin); afterTraverse << coordsToPixels(keyMax, valueMax); break; } case 8: { beforeTraverse << coordsToPixels(keyMin, valueMin); break; } } break; } case 4: { switch (currentRegion) { case 3: { afterTraverse << coordsToPixels(keyMin, valueMin); break; } case 9: { afterTraverse << coordsToPixels(keyMax, valueMin); break; } } break; } case 5: { break; // shouldn't happen because this method only handles full traverses } case 6: { switch (currentRegion) { case 1: { afterTraverse << coordsToPixels(keyMin, valueMax); break; } case 7: { afterTraverse << coordsToPixels(keyMax, valueMax); break; } } break; } case 7: { switch (currentRegion) { case 2: { beforeTraverse << coordsToPixels(keyMax, valueMax); break; } case 3: { beforeTraverse << coordsToPixels(keyMax, valueMax); afterTraverse << coordsToPixels(keyMin, valueMin); break; } case 6: { beforeTraverse << coordsToPixels(keyMax, valueMax); break; } } break; } case 8: { switch (currentRegion) { case 1: { afterTraverse << coordsToPixels(keyMin, valueMax); break; } case 3: { afterTraverse << coordsToPixels(keyMin, valueMin); break; } } break; } case 9: { switch (currentRegion) { case 2: { beforeTraverse << coordsToPixels(keyMax, valueMin); break; } case 1: { beforeTraverse << coordsToPixels(keyMax, valueMin); afterTraverse << coordsToPixels(keyMin, valueMax); break; } case 4: { beforeTraverse << coordsToPixels(keyMax, valueMin); break; } } break; } } } /*! \internal Calculates the (minimum) distance (in pixels) the curve's representation has from the given \a pixelPoint in pixels. This is used to determine whether the curve was clicked or not, e.g. in \ref selectTest. The closest data point to \a pixelPoint is returned in \a closestData. Note that if the curve has a line representation, the returned distance may be smaller than the distance to the \a closestData point, since the distance to the curve line is also taken into account. If either the curve has no data or if the line style is \ref lsNone and the scatter style's shape is \ref QCPScatterStyle::ssNone (i.e. there is no visual representation of the curve), returns -1.0. */ double QCPCurve::pointDistance(const QPointF &pixelPoint, QCPCurveDataContainer::const_iterator &closestData) const { closestData = mDataContainer->constEnd(); if (mDataContainer->isEmpty()) return -1.0; if (mLineStyle == lsNone && mScatterStyle.isNone()) return -1.0; if (mDataContainer->size() == 1) { QPointF dataPoint = coordsToPixels(mDataContainer->constBegin()->key, mDataContainer->constBegin()->value); closestData = mDataContainer->constBegin(); return QCPVector2D(dataPoint - pixelPoint).length(); } // calculate minimum distances to curve data points and find closestData iterator: double minDistSqr = std::numeric_limits::max(); // iterate over found data points and then choose the one with the shortest distance to pos: QCPCurveDataContainer::const_iterator begin = mDataContainer->constBegin(); QCPCurveDataContainer::const_iterator end = mDataContainer->constEnd(); for (QCPCurveDataContainer::const_iterator it = begin; it != end; ++it) { const double currentDistSqr = QCPVector2D(coordsToPixels(it->key, it->value) - pixelPoint).lengthSquared(); if (currentDistSqr < minDistSqr) { minDistSqr = currentDistSqr; closestData = it; } } // calculate distance to line if there is one (if so, will probably be smaller than distance to closest data point): if (mLineStyle != lsNone) { QVector lines; getCurveLines( &lines, QCPDataRange(0, dataCount()), mParentPlot->selectionTolerance() * 1.2); // optimized lines outside axis rect shouldn't respond to clicks at the edge, so use 1.2*tolerance as pen width for (int i = 0; i < lines.size() - 1; ++i) { double currentDistSqr = QCPVector2D(pixelPoint).distanceSquaredToLine(lines.at(i), lines.at(i + 1)); if (currentDistSqr < minDistSqr) minDistSqr = currentDistSqr; } } return qSqrt(minDistSqr); } /* end of 'src/plottables/plottable-curve.cpp' */ /* including file 'src/plottables/plottable-bars.cpp', size 43512 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPBarsGroup //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPBarsGroup \brief Groups multiple QCPBars together so they appear side by side \image html QCPBarsGroup.png When showing multiple QCPBars in one plot which have bars at identical keys, it may be desirable to have them appearing next to each other at each key. This is what adding the respective QCPBars plottables to a QCPBarsGroup achieves. (An alternative approach is to stack them on top of each other, see \ref QCPBars::moveAbove.) \section qcpbarsgroup-usage Usage To add a QCPBars plottable to the group, create a new group and then add the respective bars intances: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbarsgroup-creation Alternatively to appending to the group like shown above, you can also set the group on the QCPBars plottable via \ref QCPBars::setBarsGroup. The spacing between the bars can be configured via \ref setSpacingType and \ref setSpacing. The bars in this group appear in the plot in the order they were appended. To insert a bars plottable at a certain index position, or to reposition a bars plottable which is already in the group, use \ref insert. To remove specific bars from the group, use either \ref remove or call \ref QCPBars::setBarsGroup "QCPBars::setBarsGroup(0)" on the respective bars plottable. To clear the entire group, call \ref clear, or simply delete the group. \section qcpbarsgroup-example Example The image above is generated with the following code: \snippet documentation/doc-image-generator/mainwindow.cpp qcpbarsgroup-example */ /* start of documentation of inline functions */ /*! \fn QList QCPBarsGroup::bars() const Returns all bars currently in this group. \see bars(int index) */ /*! \fn int QCPBarsGroup::size() const Returns the number of QCPBars plottables that are part of this group. */ /*! \fn bool QCPBarsGroup::isEmpty() const Returns whether this bars group is empty. \see size */ /*! \fn bool QCPBarsGroup::contains(QCPBars *bars) Returns whether the specified \a bars plottable is part of this group. */ /* end of documentation of inline functions */ /*! Constructs a new bars group for the specified QCustomPlot instance. */ QCPBarsGroup::QCPBarsGroup(QCustomPlot *parentPlot) : QObject(parentPlot), mParentPlot(parentPlot), mSpacingType(stAbsolute), mSpacing(4) { } QCPBarsGroup::~QCPBarsGroup() { clear(); } /*! Sets how the spacing between adjacent bars is interpreted. See \ref SpacingType. The actual spacing can then be specified with \ref setSpacing. \see setSpacing */ void QCPBarsGroup::setSpacingType(SpacingType spacingType) { mSpacingType = spacingType; } /*! Sets the spacing between adjacent bars. What the number passed as \a spacing actually means, is defined by the current \ref SpacingType, which can be set with \ref setSpacingType. \see setSpacingType */ void QCPBarsGroup::setSpacing(double spacing) { mSpacing = spacing; } /*! Returns the QCPBars instance with the specified \a index in this group. If no such QCPBars exists, returns 0. \see bars(), size */ QCPBars *QCPBarsGroup::bars(int index) const { if (index >= 0 && index < mBars.size()) { return mBars.at(index); } else { qDebug() << Q_FUNC_INFO << "index out of bounds:" << index; - return 0; + return nullptr; } } /*! Removes all QCPBars plottables from this group. \see isEmpty */ void QCPBarsGroup::clear() { foreach (QCPBars *bars, mBars) // since foreach takes a copy, removing bars in the loop is okay - bars->setBarsGroup(0); // removes itself via removeBars + bars->setBarsGroup(nullptr); // removes itself via removeBars } /*! Adds the specified \a bars plottable to this group. Alternatively, you can also use \ref QCPBars::setBarsGroup on the \a bars instance. \see insert, remove */ void QCPBarsGroup::append(QCPBars *bars) { if (!bars) { qDebug() << Q_FUNC_INFO << "bars is 0"; return; } if (!mBars.contains(bars)) bars->setBarsGroup(this); else qDebug() << Q_FUNC_INFO << "bars plottable is already in this bars group:" << reinterpret_cast(bars); } /*! Inserts the specified \a bars plottable into this group at the specified index position \a i. This gives you full control over the ordering of the bars. \a bars may already be part of this group. In that case, \a bars is just moved to the new index position. \see append, remove */ void QCPBarsGroup::insert(int i, QCPBars *bars) { if (!bars) { qDebug() << Q_FUNC_INFO << "bars is 0"; return; } // first append to bars list normally: if (!mBars.contains(bars)) bars->setBarsGroup(this); // then move to according position: mBars.move(mBars.indexOf(bars), qBound(0, i, mBars.size() - 1)); } /*! Removes the specified \a bars plottable from this group. \see contains, clear */ void QCPBarsGroup::remove(QCPBars *bars) { if (!bars) { qDebug() << Q_FUNC_INFO << "bars is 0"; return; } if (mBars.contains(bars)) - bars->setBarsGroup(0); + bars->setBarsGroup(nullptr); else qDebug() << Q_FUNC_INFO << "bars plottable is not in this bars group:" << reinterpret_cast(bars); } /*! \internal Adds the specified \a bars to the internal mBars list of bars. This method does not change the barsGroup property on \a bars. \see unregisterBars */ void QCPBarsGroup::registerBars(QCPBars *bars) { if (!mBars.contains(bars)) mBars.append(bars); } /*! \internal Removes the specified \a bars from the internal mBars list of bars. This method does not change the barsGroup property on \a bars. \see registerBars */ void QCPBarsGroup::unregisterBars(QCPBars *bars) { mBars.removeOne(bars); } /*! \internal Returns the pixel offset in the key dimension the specified \a bars plottable should have at the given key coordinate \a keyCoord. The offset is relative to the pixel position of the key coordinate \a keyCoord. */ double QCPBarsGroup::keyPixelOffset(const QCPBars *bars, double keyCoord) { // find list of all base bars in case some mBars are stacked: QList baseBars; foreach (const QCPBars *b, mBars) { while (b->barBelow()) b = b->barBelow(); if (!baseBars.contains(b)) baseBars.append(b); } // find base bar this "bars" is stacked on: const QCPBars *thisBase = bars; while (thisBase->barBelow()) thisBase = thisBase->barBelow(); // determine key pixel offset of this base bars considering all other base bars in this barsgroup: double result = 0; int index = baseBars.indexOf(thisBase); if (index >= 0) { if (baseBars.size() % 2 == 1 && index == (baseBars.size() - 1) / 2) // is center bar (int division on purpose) { return result; } else { double lowerPixelWidth, upperPixelWidth; int startIndex; int dir = (index <= (baseBars.size() - 1) / 2) ? -1 : 1; // if bar is to lower keys of center, dir is negative if (baseBars.size() % 2 == 0) // even number of bars { startIndex = baseBars.size() / 2 + (dir < 0 ? -1 : 0); result += getPixelSpacing(baseBars.at(startIndex), keyCoord) * 0.5; // half of middle spacing } else // uneven number of bars { startIndex = (baseBars.size() - 1) / 2 + dir; baseBars.at((baseBars.size() - 1) / 2)->getPixelWidth(keyCoord, lowerPixelWidth, upperPixelWidth); result += qAbs(upperPixelWidth - lowerPixelWidth) * 0.5; // half of center bar result += getPixelSpacing(baseBars.at((baseBars.size() - 1) / 2), keyCoord); // center bar spacing } for (int i = startIndex; i != index; i += dir) // add widths and spacings of bars in between center and our bars { baseBars.at(i)->getPixelWidth(keyCoord, lowerPixelWidth, upperPixelWidth); result += qAbs(upperPixelWidth - lowerPixelWidth); result += getPixelSpacing(baseBars.at(i), keyCoord); } // finally half of our bars width: baseBars.at(index)->getPixelWidth(keyCoord, lowerPixelWidth, upperPixelWidth); result += qAbs(upperPixelWidth - lowerPixelWidth) * 0.5; // correct sign of result depending on orientation and direction of key axis: result *= dir * thisBase->keyAxis()->pixelOrientation(); } } return result; } /*! \internal Returns the spacing in pixels which is between this \a bars and the following one, both at the key coordinate \a keyCoord. \note Typically the returned value doesn't depend on \a bars or \a keyCoord. \a bars is only needed to get access to the key axis transformation and axis rect for the modes \ref stAxisRectRatio and \ref stPlotCoords. The \a keyCoord is only relevant for spacings given in \ref stPlotCoords on a logarithmic axis. */ double QCPBarsGroup::getPixelSpacing(const QCPBars *bars, double keyCoord) { switch (mSpacingType) { case stAbsolute: { return mSpacing; } case stAxisRectRatio: { if (bars->keyAxis()->orientation() == Qt::Horizontal) return bars->keyAxis()->axisRect()->width() * mSpacing; else return bars->keyAxis()->axisRect()->height() * mSpacing; } case stPlotCoords: { double keyPixel = bars->keyAxis()->coordToPixel(keyCoord); return qAbs(bars->keyAxis()->coordToPixel(keyCoord + mSpacing) - keyPixel); } } return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPBarsData //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPBarsData \brief Holds the data of one single data point (one bar) for QCPBars. The stored data is: \li \a key: coordinate on the key axis of this bar (this is the \a mainKey and the \a sortKey) \li \a value: height coordinate on the value axis of this bar (this is the \a mainValue) The container for storing multiple data points is \ref QCPBarsDataContainer. It is a typedef for \ref QCPDataContainer with \ref QCPBarsData as the DataType template parameter. See the documentation there for an explanation regarding the data type's generic methods. \see QCPBarsDataContainer */ /* start documentation of inline functions */ /*! \fn double QCPBarsData::sortKey() const Returns the \a key member of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn static QCPBarsData QCPBarsData::fromSortKey(double sortKey) Returns a data point with the specified \a sortKey. All other members are set to zero. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn static static bool QCPBarsData::sortKeyIsMainKey() Since the member \a key is both the data point key coordinate and the data ordering parameter, this method returns true. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn double QCPBarsData::mainKey() const Returns the \a key member of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn double QCPBarsData::mainValue() const Returns the \a value member of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn QCPRange QCPBarsData::valueRange() const Returns a QCPRange with both lower and upper boundary set to \a value of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /* end documentation of inline functions */ /*! Constructs a bar data point with key and value set to zero. */ QCPBarsData::QCPBarsData() : key(0), value(0) { } /*! Constructs a bar data point with the specified \a key and \a value. */ QCPBarsData::QCPBarsData(double key, double value) : key(key), value(value) { } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPBars //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPBars \brief A plottable representing a bar chart in a plot. \image html QCPBars.png To plot data, assign it with the \ref setData or \ref addData functions. \section qcpbars-appearance Changing the appearance The appearance of the bars is determined by the pen and the brush (\ref setPen, \ref setBrush). The width of the individual bars can be controlled with \ref setWidthType and \ref setWidth. Bar charts are stackable. This means, two QCPBars plottables can be placed on top of each other (see \ref QCPBars::moveAbove). So when two bars are at the same key position, they will appear stacked. If you would like to group multiple QCPBars plottables together so they appear side by side as shown below, use QCPBarsGroup. \image html QCPBarsGroup.png \section qcpbars-usage Usage Like all data representing objects in QCustomPlot, the QCPBars is a plottable (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.) Usually, you first create an instance: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-creation-1 which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead. The newly created plottable can be modified, e.g.: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-creation-2 */ /* start of documentation of inline functions */ /*! \fn QSharedPointer QCPBars::data() const Returns a shared pointer to the internal data storage of type \ref QCPBarsDataContainer. You may use it to directly manipulate the data, which may be more convenient and faster than using the regular \ref setData or \ref addData methods. */ /*! \fn QCPBars *QCPBars::barBelow() const Returns the bars plottable that is directly below this bars plottable. If there is no such plottable, returns 0. \see barAbove, moveBelow, moveAbove */ /*! \fn QCPBars *QCPBars::barAbove() const Returns the bars plottable that is directly above this bars plottable. If there is no such plottable, returns 0. \see barBelow, moveBelow, moveAbove */ /* end of documentation of inline functions */ /*! Constructs a bar chart which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have the same orientation. If either of these restrictions is violated, a corresponding message is printed to the debug output (qDebug), the construction is not aborted, though. The created QCPBars is automatically registered with the QCustomPlot instance inferred from \a keyAxis. This QCustomPlot instance takes ownership of the QCPBars, so do not delete it manually but use QCustomPlot::removePlottable() instead. */ QCPBars::QCPBars(QCPAxis *keyAxis, QCPAxis *valueAxis) - : QCPAbstractPlottable1D(keyAxis, valueAxis), mWidth(0.75), mWidthType(wtPlotCoords), mBarsGroup(0), + : QCPAbstractPlottable1D(keyAxis, valueAxis), mWidth(0.75), mWidthType(wtPlotCoords), mBarsGroup(nullptr), mBaseValue(0), mStackingGap(0) { // modify inherited properties from abstract plottable: mPen.setColor(Qt::blue); mPen.setStyle(Qt::SolidLine); mBrush.setColor(QColor(40, 50, 255, 30)); mBrush.setStyle(Qt::SolidPattern); mSelectionDecorator->setBrush(QBrush(QColor(160, 160, 255))); } QCPBars::~QCPBars() { - setBarsGroup(0); + setBarsGroup(nullptr); if (mBarBelow || mBarAbove) connectBars(mBarBelow.data(), mBarAbove.data()); // take this bar out of any stacking } /*! \overload Replaces the current data container with the provided \a data container. Since a QSharedPointer is used, multiple QCPBars may share the same data container safely. Modifying the data in the container will then affect all bars that share the container. Sharing can be achieved by simply exchanging the data containers wrapped in shared pointers: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-datasharing-1 If you do not wish to share containers, but create a copy from an existing container, rather use the \ref QCPDataContainer::set method on the bar's data container directly: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-datasharing-2 \see addData */ void QCPBars::setData(QSharedPointer data) { mDataContainer = data; } /*! \overload Replaces the current data with the provided points in \a keys and \a values. The provided vectors should have equal length. Else, the number of added points will be the size of the smallest vector. If you can guarantee that the passed data points are sorted by \a keys in ascending order, you can set \a alreadySorted to true, to improve performance by saving a sorting run. \see addData */ void QCPBars::setData(const QVector &keys, const QVector &values, bool alreadySorted) { mDataContainer->clear(); addData(keys, values, alreadySorted); } /*! Sets the width of the bars. How the number passed as \a width is interpreted (e.g. screen pixels, plot coordinates,...), depends on the currently set width type, see \ref setWidthType and \ref WidthType. */ void QCPBars::setWidth(double width) { mWidth = width; } /*! Sets how the width of the bars is defined. See the documentation of \ref WidthType for an explanation of the possible values for \a widthType. The default value is \ref wtPlotCoords. \see setWidth */ void QCPBars::setWidthType(QCPBars::WidthType widthType) { mWidthType = widthType; } /*! Sets to which QCPBarsGroup this QCPBars instance belongs to. Alternatively, you can also use \ref QCPBarsGroup::append. To remove this QCPBars from any group, set \a barsGroup to 0. */ void QCPBars::setBarsGroup(QCPBarsGroup *barsGroup) { // deregister at old group: if (mBarsGroup) mBarsGroup->unregisterBars(this); mBarsGroup = barsGroup; // register at new group: if (mBarsGroup) mBarsGroup->registerBars(this); } /*! Sets the base value of this bars plottable. The base value defines where on the value coordinate the bars start. How far the bars extend from the base value is given by their individual value data. For example, if the base value is set to 1, a bar with data value 2 will have its lowest point at value coordinate 1 and highest point at 3. For stacked bars, only the base value of the bottom-most QCPBars has meaning. The default base value is 0. */ void QCPBars::setBaseValue(double baseValue) { mBaseValue = baseValue; } /*! If this bars plottable is stacked on top of another bars plottable (\ref moveAbove), this method allows specifying a distance in \a pixels, by which the drawn bar rectangles will be separated by the bars below it. */ void QCPBars::setStackingGap(double pixels) { mStackingGap = pixels; } /*! \overload Adds the provided points in \a keys and \a values to the current data. The provided vectors should have equal length. Else, the number of added points will be the size of the smallest vector. If you can guarantee that the passed data points are sorted by \a keys in ascending order, you can set \a alreadySorted to true, to improve performance by saving a sorting run. Alternatively, you can also access and modify the data directly via the \ref data method, which returns a pointer to the internal data container. */ void QCPBars::addData(const QVector &keys, const QVector &values, bool alreadySorted) { if (keys.size() != values.size()) qDebug() << Q_FUNC_INFO << "keys and values have different sizes:" << keys.size() << values.size(); const int n = qMin(keys.size(), values.size()); QVector tempData(n); QVector::iterator it = tempData.begin(); const QVector::iterator itEnd = tempData.end(); int i = 0; while (it != itEnd) { it->key = keys[i]; it->value = values[i]; ++it; ++i; } mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write } /*! \overload Adds the provided data point as \a key and \a value to the current data. Alternatively, you can also access and modify the data directly via the \ref data method, which returns a pointer to the internal data container. */ void QCPBars::addData(double key, double value) { mDataContainer->add(QCPBarsData(key, value)); } /*! Moves this bars plottable below \a bars. In other words, the bars of this plottable will appear below the bars of \a bars. The move target \a bars must use the same key and value axis as this plottable. Inserting into and removing from existing bar stacking is handled gracefully. If \a bars already has a bars object below itself, this bars object is inserted between the two. If this bars object is already between two other bars, the two other bars will be stacked on top of each other after the operation. To remove this bars plottable from any stacking, set \a bars to 0. \see moveBelow, barAbove, barBelow */ void QCPBars::moveBelow(QCPBars *bars) { if (bars == this) return; if (bars && (bars->keyAxis() != mKeyAxis.data() || bars->valueAxis() != mValueAxis.data())) { qDebug() << Q_FUNC_INFO << "passed QCPBars* doesn't have same key and value axis as this QCPBars"; return; } // remove from stacking: connectBars(mBarBelow.data(), mBarAbove.data()); // Note: also works if one (or both) of them is 0 // if new bar given, insert this bar below it: if (bars) { if (bars->mBarBelow) connectBars(bars->mBarBelow.data(), this); connectBars(this, bars); } } /*! Moves this bars plottable above \a bars. In other words, the bars of this plottable will appear above the bars of \a bars. The move target \a bars must use the same key and value axis as this plottable. Inserting into and removing from existing bar stacking is handled gracefully. If \a bars already has a bars object above itself, this bars object is inserted between the two. If this bars object is already between two other bars, the two other bars will be stacked on top of each other after the operation. To remove this bars plottable from any stacking, set \a bars to 0. \see moveBelow, barBelow, barAbove */ void QCPBars::moveAbove(QCPBars *bars) { if (bars == this) return; if (bars && (bars->keyAxis() != mKeyAxis.data() || bars->valueAxis() != mValueAxis.data())) { qDebug() << Q_FUNC_INFO << "passed QCPBars* doesn't have same key and value axis as this QCPBars"; return; } // remove from stacking: connectBars(mBarBelow.data(), mBarAbove.data()); // Note: also works if one (or both) of them is 0 // if new bar given, insert this bar above it: if (bars) { if (bars->mBarAbove) connectBars(this, bars->mBarAbove.data()); connectBars(bars, this); } } /*! \copydoc QCPPlottableInterface1D::selectTestRect */ QCPDataSelection QCPBars::selectTestRect(const QRectF &rect, bool onlySelectable) const { QCPDataSelection result; if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty()) return result; if (!mKeyAxis || !mValueAxis) return result; QCPBarsDataContainer::const_iterator visibleBegin, visibleEnd; getVisibleDataBounds(visibleBegin, visibleEnd); for (QCPBarsDataContainer::const_iterator it = visibleBegin; it != visibleEnd; ++it) { if (rect.intersects(getBarRect(it->key, it->value))) result.addDataRange(QCPDataRange(it - mDataContainer->constBegin(), it - mDataContainer->constBegin() + 1), false); } result.simplify(); return result; } /* inherits documentation from base class */ double QCPBars::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty()) return -1; if (!mKeyAxis || !mValueAxis) return -1; if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint())) { // get visible data range: QCPBarsDataContainer::const_iterator visibleBegin, visibleEnd; getVisibleDataBounds(visibleBegin, visibleEnd); for (QCPBarsDataContainer::const_iterator it = visibleBegin; it != visibleEnd; ++it) { if (getBarRect(it->key, it->value).contains(pos)) { if (details) { int pointIndex = it - mDataContainer->constBegin(); details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex + 1))); } return mParentPlot->selectionTolerance() * 0.99; } } } return -1; } /* inherits documentation from base class */ QCPRange QCPBars::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const { /* Note: If this QCPBars uses absolute pixels as width (or is in a QCPBarsGroup with spacing in absolute pixels), using this method to adapt the key axis range to fit the bars into the currently visible axis range will not work perfectly. Because in the moment the axis range is changed to the new range, the fixed pixel widths/spacings will represent different coordinate spans than before, which in turn would require a different key range to perfectly fit, and so on. The only solution would be to iteratively approach the perfect fitting axis range, but the mismatch isn't large enough in most applications, to warrant this here. If a user does need a better fit, he should call the corresponding axis rescale multiple times in a row. */ QCPRange range; range = mDataContainer->keyRange(foundRange, inSignDomain); // determine exact range of bars by including bar width and barsgroup offset: if (foundRange && mKeyAxis) { double lowerPixelWidth, upperPixelWidth, keyPixel; // lower range bound: getPixelWidth(range.lower, lowerPixelWidth, upperPixelWidth); keyPixel = mKeyAxis.data()->coordToPixel(range.lower) + lowerPixelWidth; if (mBarsGroup) keyPixel += mBarsGroup->keyPixelOffset(this, range.lower); const double lowerCorrected = mKeyAxis.data()->pixelToCoord(keyPixel); if (!qIsNaN(lowerCorrected) && qIsFinite(lowerCorrected) && range.lower > lowerCorrected) range.lower = lowerCorrected; // upper range bound: getPixelWidth(range.upper, lowerPixelWidth, upperPixelWidth); keyPixel = mKeyAxis.data()->coordToPixel(range.upper) + upperPixelWidth; if (mBarsGroup) keyPixel += mBarsGroup->keyPixelOffset(this, range.upper); const double upperCorrected = mKeyAxis.data()->pixelToCoord(keyPixel); if (!qIsNaN(upperCorrected) && qIsFinite(upperCorrected) && range.upper < upperCorrected) range.upper = upperCorrected; } return range; } /* inherits documentation from base class */ QCPRange QCPBars::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const { // Note: can't simply use mDataContainer->valueRange here because we need to // take into account bar base value and possible stacking of multiple bars QCPRange range; range.lower = mBaseValue; range.upper = mBaseValue; bool haveLower = true; // set to true, because baseValue should always be visible in bar charts bool haveUpper = true; // set to true, because baseValue should always be visible in bar charts QCPBarsDataContainer::const_iterator itBegin = mDataContainer->constBegin(); QCPBarsDataContainer::const_iterator itEnd = mDataContainer->constEnd(); if (inKeyRange != QCPRange()) { itBegin = mDataContainer->findBegin(inKeyRange.lower); itEnd = mDataContainer->findEnd(inKeyRange.upper); } for (QCPBarsDataContainer::const_iterator it = itBegin; it != itEnd; ++it) { const double current = it->value + getStackedBaseValue(it->key, it->value >= 0); if (qIsNaN(current)) continue; if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0)) { if (current < range.lower || !haveLower) { range.lower = current; haveLower = true; } if (current > range.upper || !haveUpper) { range.upper = current; haveUpper = true; } } } foundRange = true; // return true because bar charts always have the 0-line visible return range; } /* inherits documentation from base class */ QPointF QCPBars::dataPixelPosition(int index) const { if (index >= 0 && index < mDataContainer->size()) { QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPointF(); } const QCPDataContainer::const_iterator it = mDataContainer->constBegin() + index; const double valuePixel = valueAxis->coordToPixel(getStackedBaseValue(it->key, it->value >= 0) + it->value); const double keyPixel = keyAxis->coordToPixel(it->key) + (mBarsGroup ? mBarsGroup->keyPixelOffset(this, it->key) : 0); if (keyAxis->orientation() == Qt::Horizontal) return QPointF(keyPixel, valuePixel); else return QPointF(valuePixel, keyPixel); } else { qDebug() << Q_FUNC_INFO << "Index out of bounds" << index; return QPointF(); } } /* inherits documentation from base class */ void QCPBars::draw(QCPPainter *painter) { if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; } if (mDataContainer->isEmpty()) return; QCPBarsDataContainer::const_iterator visibleBegin, visibleEnd; getVisibleDataBounds(visibleBegin, visibleEnd); // loop over and draw segments of unselected/selected data: QList selectedSegments, unselectedSegments, allSegments; getDataSegments(selectedSegments, unselectedSegments); allSegments << unselectedSegments << selectedSegments; for (int i = 0; i < allSegments.size(); ++i) { bool isSelectedSegment = i >= unselectedSegments.size(); QCPBarsDataContainer::const_iterator begin = visibleBegin; QCPBarsDataContainer::const_iterator end = visibleEnd; mDataContainer->limitIteratorsToDataRange(begin, end, allSegments.at(i)); if (begin == end) continue; for (QCPBarsDataContainer::const_iterator it = begin; it != end; ++it) { // check data validity if flag set: #ifdef QCUSTOMPLOT_CHECK_DATA if (QCP::isInvalidData(it->key, it->value)) qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "of drawn range invalid." << "Plottable name:" << name(); #endif // draw bar: if (isSelectedSegment && mSelectionDecorator) { mSelectionDecorator->applyBrush(painter); mSelectionDecorator->applyPen(painter); } else { painter->setBrush(mBrush); painter->setPen(mPen); } applyDefaultAntialiasingHint(painter); painter->drawPolygon(getBarRect(it->key, it->value)); } } // draw other selection decoration that isn't just line/scatter pens and brushes: if (mSelectionDecorator) mSelectionDecorator->drawDecoration(painter, selection()); } /* inherits documentation from base class */ void QCPBars::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const { // draw filled rect: applyDefaultAntialiasingHint(painter); painter->setBrush(mBrush); painter->setPen(mPen); QRectF r = QRectF(0, 0, rect.width() * 0.67, rect.height() * 0.67); r.moveCenter(rect.center()); painter->drawRect(r); } /*! \internal called by \ref draw to determine which data (key) range is visible at the current key axis range setting, so only that needs to be processed. It also takes into account the bar width. \a begin returns an iterator to the lowest data point that needs to be taken into account when plotting. Note that in order to get a clean plot all the way to the edge of the axis rect, \a lower may still be just outside the visible range. \a end returns an iterator one higher than the highest visible data point. Same as before, \a end may also lie just outside of the visible range. if the plottable contains no data, both \a begin and \a end point to constEnd. */ void QCPBars::getVisibleDataBounds(QCPBarsDataContainer::const_iterator &begin, QCPBarsDataContainer::const_iterator &end) const { if (!mKeyAxis) { qDebug() << Q_FUNC_INFO << "invalid key axis"; begin = mDataContainer->constEnd(); end = mDataContainer->constEnd(); return; } if (mDataContainer->isEmpty()) { begin = mDataContainer->constEnd(); end = mDataContainer->constEnd(); return; } // get visible data range as QMap iterators begin = mDataContainer->findBegin(mKeyAxis.data()->range().lower); end = mDataContainer->findEnd(mKeyAxis.data()->range().upper); double lowerPixelBound = mKeyAxis.data()->coordToPixel(mKeyAxis.data()->range().lower); double upperPixelBound = mKeyAxis.data()->coordToPixel(mKeyAxis.data()->range().upper); bool isVisible = false; // walk left from begin to find lower bar that actually is completely outside visible pixel range: QCPBarsDataContainer::const_iterator it = begin; while (it != mDataContainer->constBegin()) { --it; const QRectF barRect = getBarRect(it->key, it->value); if (mKeyAxis.data()->orientation() == Qt::Horizontal) isVisible = ((!mKeyAxis.data()->rangeReversed() && barRect.right() >= lowerPixelBound) || (mKeyAxis.data()->rangeReversed() && barRect.left() <= lowerPixelBound)); else // keyaxis is vertical isVisible = ((!mKeyAxis.data()->rangeReversed() && barRect.top() <= lowerPixelBound) || (mKeyAxis.data()->rangeReversed() && barRect.bottom() >= lowerPixelBound)); if (isVisible) begin = it; else break; } // walk right from ubound to find upper bar that actually is completely outside visible pixel range: it = end; while (it != mDataContainer->constEnd()) { const QRectF barRect = getBarRect(it->key, it->value); if (mKeyAxis.data()->orientation() == Qt::Horizontal) isVisible = ((!mKeyAxis.data()->rangeReversed() && barRect.left() <= upperPixelBound) || (mKeyAxis.data()->rangeReversed() && barRect.right() >= upperPixelBound)); else // keyaxis is vertical isVisible = ((!mKeyAxis.data()->rangeReversed() && barRect.bottom() >= upperPixelBound) || (mKeyAxis.data()->rangeReversed() && barRect.top() <= upperPixelBound)); if (isVisible) end = it + 1; else break; ++it; } } /*! \internal Returns the rect in pixel coordinates of a single bar with the specified \a key and \a value. The rect is shifted according to the bar stacking (see \ref moveAbove) and base value (see \ref setBaseValue), and to have non-overlapping border lines with the bars stacked below. */ QRectF QCPBars::getBarRect(double key, double value) const { QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QRectF(); } double lowerPixelWidth, upperPixelWidth; getPixelWidth(key, lowerPixelWidth, upperPixelWidth); double base = getStackedBaseValue(key, value >= 0); double basePixel = valueAxis->coordToPixel(base); double valuePixel = valueAxis->coordToPixel(base + value); double keyPixel = keyAxis->coordToPixel(key); if (mBarsGroup) keyPixel += mBarsGroup->keyPixelOffset(this, key); double bottomOffset = (mBarBelow && mPen != Qt::NoPen ? 1 : 0) * (mPen.isCosmetic() ? 1 : mPen.widthF()); bottomOffset += mBarBelow ? mStackingGap : 0; bottomOffset *= (value < 0 ? -1 : 1) * valueAxis->pixelOrientation(); if (qAbs(valuePixel - basePixel) <= qAbs(bottomOffset)) bottomOffset = valuePixel - basePixel; if (keyAxis->orientation() == Qt::Horizontal) { return QRectF(QPointF(keyPixel + lowerPixelWidth, valuePixel), QPointF(keyPixel + upperPixelWidth, basePixel + bottomOffset)) .normalized(); } else { return QRectF(QPointF(basePixel + bottomOffset, keyPixel + lowerPixelWidth), QPointF(valuePixel, keyPixel + upperPixelWidth)) .normalized(); } } /*! \internal This function is used to determine the width of the bar at coordinate \a key, according to the specified width (\ref setWidth) and width type (\ref setWidthType). The output parameters \a lower and \a upper return the number of pixels the bar extends to lower and higher keys, relative to the \a key coordinate (so with a non-reversed horizontal axis, \a lower is negative and \a upper positive). */ void QCPBars::getPixelWidth(double key, double &lower, double &upper) const { lower = 0; upper = 0; switch (mWidthType) { case wtAbsolute: { upper = mWidth * 0.5 * mKeyAxis.data()->pixelOrientation(); lower = -upper; break; } case wtAxisRectRatio: { if (mKeyAxis && mKeyAxis.data()->axisRect()) { if (mKeyAxis.data()->orientation() == Qt::Horizontal) upper = mKeyAxis.data()->axisRect()->width() * mWidth * 0.5 * mKeyAxis.data()->pixelOrientation(); else upper = mKeyAxis.data()->axisRect()->height() * mWidth * 0.5 * mKeyAxis.data()->pixelOrientation(); lower = -upper; } else qDebug() << Q_FUNC_INFO << "No key axis or axis rect defined"; break; } case wtPlotCoords: { if (mKeyAxis) { double keyPixel = mKeyAxis.data()->coordToPixel(key); upper = mKeyAxis.data()->coordToPixel(key + mWidth * 0.5) - keyPixel; lower = mKeyAxis.data()->coordToPixel(key - mWidth * 0.5) - keyPixel; // no need to qSwap(lower, higher) when range reversed, because higher/lower are gained by // coordinate transform which includes range direction } else qDebug() << Q_FUNC_INFO << "No key axis defined"; break; } } } /*! \internal This function is called to find at which value to start drawing the base of a bar at \a key, when it is stacked on top of another QCPBars (e.g. with \ref moveAbove). positive and negative bars are separated per stack (positive are stacked above baseValue upwards, negative are stacked below baseValue downwards). This can be indicated with \a positive. So if the bar for which we need the base value is negative, set \a positive to false. */ double QCPBars::getStackedBaseValue(double key, bool positive) const { if (mBarBelow) { double max = 0; // don't initialize with mBaseValue here because only base value of bottom-most bar has meaning in a bar stack // find bars of mBarBelow that are approximately at key and find largest one: double epsilon = qAbs(key) * (sizeof(key) == 4 ? 1e-6 : 1e-14); // should be safe even when changed to use float at some point if (key == 0) epsilon = (sizeof(key) == 4 ? 1e-6 : 1e-14); QCPBarsDataContainer::const_iterator it = mBarBelow.data()->mDataContainer->findBegin(key - epsilon); QCPBarsDataContainer::const_iterator itEnd = mBarBelow.data()->mDataContainer->findEnd(key + epsilon); while (it != itEnd) { if (it->key > key - epsilon && it->key < key + epsilon) { if ((positive && it->value > max) || (!positive && it->value < max)) max = it->value; } ++it; } // recurse down the bar-stack to find the total height: return max + mBarBelow.data()->getStackedBaseValue(key, positive); } else return mBaseValue; } /*! \internal Connects \a below and \a above to each other via their mBarAbove/mBarBelow properties. The bar(s) currently above lower and below upper will become disconnected to lower/upper. If lower is zero, upper will be disconnected at the bottom. If upper is zero, lower will be disconnected at the top. */ void QCPBars::connectBars(QCPBars *lower, QCPBars *upper) { if (!lower && !upper) return; if (!lower) // disconnect upper at bottom { // disconnect old bar below upper: if (upper->mBarBelow && upper->mBarBelow.data()->mBarAbove.data() == upper) - upper->mBarBelow.data()->mBarAbove = 0; - upper->mBarBelow = 0; + upper->mBarBelow.data()->mBarAbove = nullptr; + upper->mBarBelow = nullptr; } else if (!upper) // disconnect lower at top { // disconnect old bar above lower: if (lower->mBarAbove && lower->mBarAbove.data()->mBarBelow.data() == lower) - lower->mBarAbove.data()->mBarBelow = 0; - lower->mBarAbove = 0; + lower->mBarAbove.data()->mBarBelow = nullptr; + lower->mBarAbove = nullptr; } else // connect lower and upper { // disconnect old bar above lower: if (lower->mBarAbove && lower->mBarAbove.data()->mBarBelow.data() == lower) - lower->mBarAbove.data()->mBarBelow = 0; + lower->mBarAbove.data()->mBarBelow = nullptr; // disconnect old bar below upper: if (upper->mBarBelow && upper->mBarBelow.data()->mBarAbove.data() == upper) - upper->mBarBelow.data()->mBarAbove = 0; + upper->mBarBelow.data()->mBarAbove = nullptr; lower->mBarAbove = upper; upper->mBarBelow = lower; } } /* end of 'src/plottables/plottable-bars.cpp' */ /* including file 'src/plottables/plottable-statisticalbox.cpp', size 28622 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPStatisticalBoxData //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPStatisticalBoxData \brief Holds the data of one single data point for QCPStatisticalBox. The stored data is: \li \a key: coordinate on the key axis of this data point (this is the \a mainKey and the \a sortKey) \li \a minimum: the position of the lower whisker, typically the minimum measurement of the sample that's not considered an outlier. \li \a lowerQuartile: the lower end of the box. The lower and the upper quartiles are the two statistical quartiles around the median of the sample, they should contain 50% of the sample data. \li \a median: the value of the median mark inside the quartile box. The median separates the sample data in half (50% of the sample data is below/above the median). (This is the \a mainValue) \li \a upperQuartile: the upper end of the box. The lower and the upper quartiles are the two statistical quartiles around the median of the sample, they should contain 50% of the sample data. \li \a maximum: the position of the upper whisker, typically the maximum measurement of the sample that's not considered an outlier. \li \a outliers: a QVector of outlier values that will be drawn as scatter points at the \a key coordinate of this data point (see \ref QCPStatisticalBox::setOutlierStyle) The container for storing multiple data points is \ref QCPStatisticalBoxDataContainer. It is a typedef for \ref QCPDataContainer with \ref QCPStatisticalBoxData as the DataType template parameter. See the documentation there for an explanation regarding the data type's generic methods. \see QCPStatisticalBoxDataContainer */ /* start documentation of inline functions */ /*! \fn double QCPStatisticalBoxData::sortKey() const Returns the \a key member of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn static QCPStatisticalBoxData QCPStatisticalBoxData::fromSortKey(double sortKey) Returns a data point with the specified \a sortKey. All other members are set to zero. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn static static bool QCPStatisticalBoxData::sortKeyIsMainKey() Since the member \a key is both the data point key coordinate and the data ordering parameter, this method returns true. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn double QCPStatisticalBoxData::mainKey() const Returns the \a key member of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn double QCPStatisticalBoxData::mainValue() const Returns the \a median member of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn QCPRange QCPStatisticalBoxData::valueRange() const Returns a QCPRange spanning from the \a minimum to the \a maximum member of this statistical box data point, possibly further expanded by outliers. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /* end documentation of inline functions */ /*! Constructs a data point with key and all values set to zero. */ QCPStatisticalBoxData::QCPStatisticalBoxData() : key(0), minimum(0), lowerQuartile(0), median(0), upperQuartile(0), maximum(0) { } /*! Constructs a data point with the specified \a key, \a minimum, \a lowerQuartile, \a median, \a upperQuartile, \a maximum and optionally a number of \a outliers. */ QCPStatisticalBoxData::QCPStatisticalBoxData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum, const QVector &outliers) : key(key), minimum(minimum), lowerQuartile(lowerQuartile), median(median), upperQuartile(upperQuartile), maximum(maximum), outliers(outliers) { } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPStatisticalBox //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPStatisticalBox \brief A plottable representing a single statistical box in a plot. \image html QCPStatisticalBox.png To plot data, assign it with the \ref setData or \ref addData functions. Alternatively, you can also access and modify the data via the \ref data method, which returns a pointer to the internal \ref QCPStatisticalBoxDataContainer. Additionally each data point can itself have a list of outliers, drawn as scatter points at the key coordinate of the respective statistical box data point. They can either be set by using the respective \ref addData(double,double,double,double,double,double,const QVector&) "addData" method or accessing the individual data points through \ref data, and setting the QVector outliers of the data points directly. \section qcpstatisticalbox-appearance Changing the appearance The appearance of each data point box, ranging from the lower to the upper quartile, is controlled via \ref setPen and \ref setBrush. You may change the width of the boxes with \ref setWidth in plot coordinates. Each data point's visual representation also consists of two whiskers. Whiskers are the lines which reach from the upper quartile to the maximum, and from the lower quartile to the minimum. The appearance of the whiskers can be modified with: \ref setWhiskerPen, \ref setWhiskerBarPen, \ref setWhiskerWidth. The whisker width is the width of the bar perpendicular to the whisker at the top (for maximum) and bottom (for minimum). If the whisker pen is changed, make sure to set the \c capStyle to \c Qt::FlatCap. Otherwise the backbone line might exceed the whisker bars by a few pixels due to the pen cap being not perfectly flat. The median indicator line inside the box has its own pen, \ref setMedianPen. The outlier data points are drawn as normal scatter points. Their look can be controlled with \ref setOutlierStyle \section qcpstatisticalbox-usage Usage Like all data representing objects in QCustomPlot, the QCPStatisticalBox is a plottable (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.) Usually, you first create an instance: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-creation-1 which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead. The newly created plottable can be modified, e.g.: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-creation-2 */ /* start documentation of inline functions */ /*! \fn QSharedPointer QCPStatisticalBox::data() const Returns a shared pointer to the internal data storage of type \ref QCPStatisticalBoxDataContainer. You may use it to directly manipulate the data, which may be more convenient and faster than using the regular \ref setData or \ref addData methods. */ /* end documentation of inline functions */ /*! Constructs a statistical box which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have the same orientation. If either of these restrictions is violated, a corresponding message is printed to the debug output (qDebug), the construction is not aborted, though. The created QCPStatisticalBox is automatically registered with the QCustomPlot instance inferred from \a keyAxis. This QCustomPlot instance takes ownership of the QCPStatisticalBox, so do not delete it manually but use QCustomPlot::removePlottable() instead. */ QCPStatisticalBox::QCPStatisticalBox(QCPAxis *keyAxis, QCPAxis *valueAxis) : QCPAbstractPlottable1D(keyAxis, valueAxis), mWidth(0.5), mWhiskerWidth(0.2), mWhiskerPen(Qt::black, 0, Qt::DashLine, Qt::FlatCap), mWhiskerBarPen(Qt::black), mWhiskerAntialiased(false), mMedianPen(Qt::black, 3, Qt::SolidLine, Qt::FlatCap), mOutlierStyle(QCPScatterStyle::ssCircle, Qt::blue, 6) { setPen(QPen(Qt::black)); setBrush(Qt::NoBrush); } /*! \overload Replaces the current data container with the provided \a data container. Since a QSharedPointer is used, multiple QCPStatisticalBoxes may share the same data container safely. Modifying the data in the container will then affect all statistical boxes that share the container. Sharing can be achieved by simply exchanging the data containers wrapped in shared pointers: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-datasharing-1 If you do not wish to share containers, but create a copy from an existing container, rather use the \ref QCPDataContainer::set method on the statistical box data container directly: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-datasharing-2 \see addData */ void QCPStatisticalBox::setData(QSharedPointer data) { mDataContainer = data; } /*! \overload Replaces the current data with the provided points in \a keys, \a minimum, \a lowerQuartile, \a median, \a upperQuartile and \a maximum. The provided vectors should have equal length. Else, the number of added points will be the size of the smallest vector. If you can guarantee that the passed data points are sorted by \a keys in ascending order, you can set \a alreadySorted to true, to improve performance by saving a sorting run. \see addData */ void QCPStatisticalBox::setData(const QVector &keys, const QVector &minimum, const QVector &lowerQuartile, const QVector &median, const QVector &upperQuartile, const QVector &maximum, bool alreadySorted) { mDataContainer->clear(); addData(keys, minimum, lowerQuartile, median, upperQuartile, maximum, alreadySorted); } /*! Sets the width of the boxes in key coordinates. \see setWhiskerWidth */ void QCPStatisticalBox::setWidth(double width) { mWidth = width; } /*! Sets the width of the whiskers in key coordinates. Whiskers are the lines which reach from the upper quartile to the maximum, and from the lower quartile to the minimum. \see setWidth */ void QCPStatisticalBox::setWhiskerWidth(double width) { mWhiskerWidth = width; } /*! Sets the pen used for drawing the whisker backbone. Whiskers are the lines which reach from the upper quartile to the maximum, and from the lower quartile to the minimum. Make sure to set the \c capStyle of the passed \a pen to \c Qt::FlatCap. Otherwise the backbone line might exceed the whisker bars by a few pixels due to the pen cap being not perfectly flat. \see setWhiskerBarPen */ void QCPStatisticalBox::setWhiskerPen(const QPen &pen) { mWhiskerPen = pen; } /*! Sets the pen used for drawing the whisker bars. Those are the lines parallel to the key axis at each end of the whisker backbone. Whiskers are the lines which reach from the upper quartile to the maximum, and from the lower quartile to the minimum. \see setWhiskerPen */ void QCPStatisticalBox::setWhiskerBarPen(const QPen &pen) { mWhiskerBarPen = pen; } /*! Sets whether the statistical boxes whiskers are drawn with antialiasing or not. Note that antialiasing settings may be overridden by QCustomPlot::setAntialiasedElements and QCustomPlot::setNotAntialiasedElements. */ void QCPStatisticalBox::setWhiskerAntialiased(bool enabled) { mWhiskerAntialiased = enabled; } /*! Sets the pen used for drawing the median indicator line inside the statistical boxes. */ void QCPStatisticalBox::setMedianPen(const QPen &pen) { mMedianPen = pen; } /*! Sets the appearance of the outlier data points. Outliers can be specified with the method \ref addData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum, const QVector &outliers) */ void QCPStatisticalBox::setOutlierStyle(const QCPScatterStyle &style) { mOutlierStyle = style; } /*! \overload Adds the provided points in \a keys, \a minimum, \a lowerQuartile, \a median, \a upperQuartile and \a maximum to the current data. The provided vectors should have equal length. Else, the number of added points will be the size of the smallest vector. If you can guarantee that the passed data points are sorted by \a keys in ascending order, you can set \a alreadySorted to true, to improve performance by saving a sorting run. Alternatively, you can also access and modify the data directly via the \ref data method, which returns a pointer to the internal data container. */ void QCPStatisticalBox::addData(const QVector &keys, const QVector &minimum, const QVector &lowerQuartile, const QVector &median, const QVector &upperQuartile, const QVector &maximum, bool alreadySorted) { if (keys.size() != minimum.size() || minimum.size() != lowerQuartile.size() || lowerQuartile.size() != median.size() || median.size() != upperQuartile.size() || upperQuartile.size() != maximum.size() || maximum.size() != keys.size()) qDebug() << Q_FUNC_INFO << "keys, minimum, lowerQuartile, median, upperQuartile, maximum have different sizes:" << keys.size() << minimum.size() << lowerQuartile.size() << median.size() << upperQuartile.size() << maximum.size(); const int n = qMin(keys.size(), qMin(minimum.size(), qMin(lowerQuartile.size(), qMin(median.size(), qMin(upperQuartile.size(), maximum.size()))))); QVector tempData(n); QVector::iterator it = tempData.begin(); const QVector::iterator itEnd = tempData.end(); int i = 0; while (it != itEnd) { it->key = keys[i]; it->minimum = minimum[i]; it->lowerQuartile = lowerQuartile[i]; it->median = median[i]; it->upperQuartile = upperQuartile[i]; it->maximum = maximum[i]; ++it; ++i; } mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write } /*! \overload Adds the provided data point as \a key, \a minimum, \a lowerQuartile, \a median, \a upperQuartile and \a maximum to the current data. Alternatively, you can also access and modify the data directly via the \ref data method, which returns a pointer to the internal data container. */ void QCPStatisticalBox::addData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum, const QVector &outliers) { mDataContainer->add(QCPStatisticalBoxData(key, minimum, lowerQuartile, median, upperQuartile, maximum, outliers)); } /*! \copydoc QCPPlottableInterface1D::selectTestRect */ QCPDataSelection QCPStatisticalBox::selectTestRect(const QRectF &rect, bool onlySelectable) const { QCPDataSelection result; if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty()) return result; if (!mKeyAxis || !mValueAxis) return result; QCPStatisticalBoxDataContainer::const_iterator visibleBegin, visibleEnd; getVisibleDataBounds(visibleBegin, visibleEnd); for (QCPStatisticalBoxDataContainer::const_iterator it = visibleBegin; it != visibleEnd; ++it) { if (rect.intersects(getQuartileBox(it))) result.addDataRange(QCPDataRange(it - mDataContainer->constBegin(), it - mDataContainer->constBegin() + 1), false); } result.simplify(); return result; } /* inherits documentation from base class */ double QCPStatisticalBox::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty()) return -1; if (!mKeyAxis || !mValueAxis) return -1; if (mKeyAxis->axisRect()->rect().contains(pos.toPoint())) { // get visible data range: QCPStatisticalBoxDataContainer::const_iterator visibleBegin, visibleEnd; QCPStatisticalBoxDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd(); getVisibleDataBounds(visibleBegin, visibleEnd); double minDistSqr = std::numeric_limits::max(); for (QCPStatisticalBoxDataContainer::const_iterator it = visibleBegin; it != visibleEnd; ++it) { if (getQuartileBox(it).contains(pos)) // quartile box { double currentDistSqr = mParentPlot->selectionTolerance() * 0.99 * mParentPlot->selectionTolerance() * 0.99; if (currentDistSqr < minDistSqr) { minDistSqr = currentDistSqr; closestDataPoint = it; } } else // whiskers { const QVector whiskerBackbones(getWhiskerBackboneLines(it)); for (int i = 0; i < whiskerBackbones.size(); ++i) { double currentDistSqr = QCPVector2D(pos).distanceSquaredToLine(whiskerBackbones.at(i)); if (currentDistSqr < minDistSqr) { minDistSqr = currentDistSqr; closestDataPoint = it; } } } } if (details) { int pointIndex = closestDataPoint - mDataContainer->constBegin(); details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex + 1))); } return qSqrt(minDistSqr); } return -1; } /* inherits documentation from base class */ QCPRange QCPStatisticalBox::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const { QCPRange range = mDataContainer->keyRange(foundRange, inSignDomain); // determine exact range by including width of bars/flags: if (foundRange) { if (inSignDomain != QCP::sdPositive || range.lower - mWidth * 0.5 > 0) range.lower -= mWidth * 0.5; if (inSignDomain != QCP::sdNegative || range.upper + mWidth * 0.5 < 0) range.upper += mWidth * 0.5; } return range; } /* inherits documentation from base class */ QCPRange QCPStatisticalBox::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const { return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange); } /* inherits documentation from base class */ void QCPStatisticalBox::draw(QCPPainter *painter) { if (mDataContainer->isEmpty()) return; QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; } QCPStatisticalBoxDataContainer::const_iterator visibleBegin, visibleEnd; getVisibleDataBounds(visibleBegin, visibleEnd); // loop over and draw segments of unselected/selected data: QList selectedSegments, unselectedSegments, allSegments; getDataSegments(selectedSegments, unselectedSegments); allSegments << unselectedSegments << selectedSegments; for (int i = 0; i < allSegments.size(); ++i) { bool isSelectedSegment = i >= unselectedSegments.size(); QCPStatisticalBoxDataContainer::const_iterator begin = visibleBegin; QCPStatisticalBoxDataContainer::const_iterator end = visibleEnd; mDataContainer->limitIteratorsToDataRange(begin, end, allSegments.at(i)); if (begin == end) continue; for (QCPStatisticalBoxDataContainer::const_iterator it = begin; it != end; ++it) { // check data validity if flag set: #ifdef QCUSTOMPLOT_CHECK_DATA if (QCP::isInvalidData(it->key, it->minimum) || QCP::isInvalidData(it->lowerQuartile, it->median) || QCP::isInvalidData(it->upperQuartile, it->maximum)) qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "of drawn range has invalid data." << "Plottable name:" << name(); for (int i = 0; i < it->outliers.size(); ++i) if (QCP::isInvalidData(it->outliers.at(i))) qDebug() << Q_FUNC_INFO << "Data point outlier at" << it->key << "of drawn range invalid." << "Plottable name:" << name(); #endif if (isSelectedSegment && mSelectionDecorator) { mSelectionDecorator->applyPen(painter); mSelectionDecorator->applyBrush(painter); } else { painter->setPen(mPen); painter->setBrush(mBrush); } QCPScatterStyle finalOutlierStyle = mOutlierStyle; if (isSelectedSegment && mSelectionDecorator) finalOutlierStyle = mSelectionDecorator->getFinalScatterStyle(mOutlierStyle); drawStatisticalBox(painter, it, finalOutlierStyle); } } // draw other selection decoration that isn't just line/scatter pens and brushes: if (mSelectionDecorator) mSelectionDecorator->drawDecoration(painter, selection()); } /* inherits documentation from base class */ void QCPStatisticalBox::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const { // draw filled rect: applyDefaultAntialiasingHint(painter); painter->setPen(mPen); painter->setBrush(mBrush); QRectF r = QRectF(0, 0, rect.width() * 0.67, rect.height() * 0.67); r.moveCenter(rect.center()); painter->drawRect(r); } /*! Draws the graphical representation of a single statistical box with the data given by the iterator \a it with the provided \a painter. If the statistical box has a set of outlier data points, they are drawn with \a outlierStyle. \see getQuartileBox, getWhiskerBackboneLines, getWhiskerBarLines */ void QCPStatisticalBox::drawStatisticalBox(QCPPainter *painter, QCPStatisticalBoxDataContainer::const_iterator it, const QCPScatterStyle &outlierStyle) const { // draw quartile box: applyDefaultAntialiasingHint(painter); const QRectF quartileBox = getQuartileBox(it); painter->drawRect(quartileBox); // draw median line with cliprect set to quartile box: painter->save(); painter->setClipRect(quartileBox, Qt::IntersectClip); painter->setPen(mMedianPen); painter->drawLine( QLineF(coordsToPixels(it->key - mWidth * 0.5, it->median), coordsToPixels(it->key + mWidth * 0.5, it->median))); painter->restore(); // draw whisker lines: applyAntialiasingHint(painter, mWhiskerAntialiased, QCP::aePlottables); painter->setPen(mWhiskerPen); painter->drawLines(getWhiskerBackboneLines(it)); painter->setPen(mWhiskerBarPen); painter->drawLines(getWhiskerBarLines(it)); // draw outliers: applyScattersAntialiasingHint(painter); outlierStyle.applyTo(painter, mPen); for (int i = 0; i < it->outliers.size(); ++i) outlierStyle.drawShape(painter, coordsToPixels(it->key, it->outliers.at(i))); } /*! \internal called by \ref draw to determine which data (key) range is visible at the current key axis range setting, so only that needs to be processed. It also takes into account the bar width. \a begin returns an iterator to the lowest data point that needs to be taken into account when plotting. Note that in order to get a clean plot all the way to the edge of the axis rect, \a lower may still be just outside the visible range. \a end returns an iterator one higher than the highest visible data point. Same as before, \a end may also lie just outside of the visible range. if the plottable contains no data, both \a begin and \a end point to constEnd. */ void QCPStatisticalBox::getVisibleDataBounds(QCPStatisticalBoxDataContainer::const_iterator &begin, QCPStatisticalBoxDataContainer::const_iterator &end) const { if (!mKeyAxis) { qDebug() << Q_FUNC_INFO << "invalid key axis"; begin = mDataContainer->constEnd(); end = mDataContainer->constEnd(); return; } begin = mDataContainer->findBegin(mKeyAxis.data()->range().lower - mWidth * 0.5); // subtract half width of box to include partially visible data points end = mDataContainer->findEnd(mKeyAxis.data()->range().upper + mWidth * 0.5); // add half width of box to include partially visible data points } /*! \internal Returns the box in plot coordinates (keys in x, values in y of the returned rect) that covers the value range from the lower to the upper quartile, of the data given by \a it. \see drawStatisticalBox, getWhiskerBackboneLines, getWhiskerBarLines */ QRectF QCPStatisticalBox::getQuartileBox(QCPStatisticalBoxDataContainer::const_iterator it) const { QRectF result; result.setTopLeft(coordsToPixels(it->key - mWidth * 0.5, it->upperQuartile)); result.setBottomRight(coordsToPixels(it->key + mWidth * 0.5, it->lowerQuartile)); return result; } /*! \internal Returns the whisker backbones (keys in x, values in y of the returned lines) that cover the value range from the minimum to the lower quartile, and from the upper quartile to the maximum of the data given by \a it. \see drawStatisticalBox, getQuartileBox, getWhiskerBarLines */ QVector QCPStatisticalBox::getWhiskerBackboneLines(QCPStatisticalBoxDataContainer::const_iterator it) const { QVector result(2); result[0].setPoints(coordsToPixels(it->key, it->lowerQuartile), coordsToPixels(it->key, it->minimum)); // min backbone result[1].setPoints(coordsToPixels(it->key, it->upperQuartile), coordsToPixels(it->key, it->maximum)); // max backbone return result; } /*! \internal Returns the whisker bars (keys in x, values in y of the returned lines) that are placed at the end of the whisker backbones, at the minimum and maximum of the data given by \a it. \see drawStatisticalBox, getQuartileBox, getWhiskerBackboneLines */ QVector QCPStatisticalBox::getWhiskerBarLines(QCPStatisticalBoxDataContainer::const_iterator it) const { QVector result(2); result[0].setPoints(coordsToPixels(it->key - mWhiskerWidth * 0.5, it->minimum), coordsToPixels(it->key + mWhiskerWidth * 0.5, it->minimum)); // min bar result[1].setPoints(coordsToPixels(it->key - mWhiskerWidth * 0.5, it->maximum), coordsToPixels(it->key + mWhiskerWidth * 0.5, it->maximum)); // max bar return result; } /* end of 'src/plottables/plottable-statisticalbox.cpp' */ /* including file 'src/plottables/plottable-colormap.cpp', size 47531 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPColorMapData //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPColorMapData \brief Holds the two-dimensional data of a QCPColorMap plottable. This class is a data storage for \ref QCPColorMap. It holds a two-dimensional array, which \ref QCPColorMap then displays as a 2D image in the plot, where the array values are represented by a color, depending on the value. The size of the array can be controlled via \ref setSize (or \ref setKeySize, \ref setValueSize). Which plot coordinates these cells correspond to can be configured with \ref setRange (or \ref setKeyRange, \ref setValueRange). The data cells can be accessed in two ways: They can be directly addressed by an integer index with \ref setCell. This is the fastest method. Alternatively, they can be addressed by their plot coordinate with \ref setData. plot coordinate to cell index transformations and vice versa are provided by the functions \ref coordToCell and \ref cellToCoord. A \ref QCPColorMapData also holds an on-demand two-dimensional array of alpha values which (if allocated) has the same size as the data map. It can be accessed via \ref setAlpha, \ref fillAlpha and \ref clearAlpha. The memory for the alpha map is only allocated if needed, i.e. on the first call of \ref setAlpha. \ref clearAlpha restores full opacity and frees the alpha map. This class also buffers the minimum and maximum values that are in the data set, to provide QCPColorMap::rescaleDataRange with the necessary information quickly. Setting a cell to a value that is greater than the current maximum increases this maximum to the new value. However, setting the cell that currently holds the maximum value to a smaller value doesn't decrease the maximum again, because finding the true new maximum would require going through the entire data array, which might be time consuming. The same holds for the data minimum. This functionality is given by \ref recalculateDataBounds, such that you can decide when it is sensible to find the true current minimum and maximum. The method QCPColorMap::rescaleDataRange offers a convenience parameter \a recalculateDataBounds which may be set to true to automatically call \ref recalculateDataBounds internally. */ /* start of documentation of inline functions */ /*! \fn bool QCPColorMapData::isEmpty() const Returns whether this instance carries no data. This is equivalent to having a size where at least one of the dimensions is 0 (see \ref setSize). */ /* end of documentation of inline functions */ /*! Constructs a new QCPColorMapData instance. The instance has \a keySize cells in the key direction and \a valueSize cells in the value direction. These cells will be displayed by the \ref QCPColorMap at the coordinates \a keyRange and \a valueRange. \see setSize, setKeySize, setValueSize, setRange, setKeyRange, setValueRange */ QCPColorMapData::QCPColorMapData(int keySize, int valueSize, const QCPRange &keyRange, const QCPRange &valueRange) - : mKeySize(0), mValueSize(0), mKeyRange(keyRange), mValueRange(valueRange), mIsEmpty(true), mData(0), mAlpha(0), + : mKeySize(0), mValueSize(0), mKeyRange(keyRange), mValueRange(valueRange), mIsEmpty(true), mData(nullptr), mAlpha(nullptr), mDataModified(true) { setSize(keySize, valueSize); fill(0); } QCPColorMapData::~QCPColorMapData() { if (mData) delete[] mData; if (mAlpha) delete[] mAlpha; } /*! Constructs a new QCPColorMapData instance copying the data and range of \a other. */ QCPColorMapData::QCPColorMapData(const QCPColorMapData &other) - : mKeySize(0), mValueSize(0), mIsEmpty(true), mData(0), mAlpha(0), mDataModified(true) + : mKeySize(0), mValueSize(0), mIsEmpty(true), mData(nullptr), mAlpha(nullptr), mDataModified(true) { *this = other; } /*! Overwrites this color map data instance with the data stored in \a other. The alpha map state is transferred, too. */ QCPColorMapData &QCPColorMapData::operator=(const QCPColorMapData &other) { if (&other != this) { const int keySize = other.keySize(); const int valueSize = other.valueSize(); if (!other.mAlpha && mAlpha) clearAlpha(); setSize(keySize, valueSize); if (other.mAlpha && !mAlpha) createAlpha(false); setRange(other.keyRange(), other.valueRange()); if (!isEmpty()) { memcpy(mData, other.mData, sizeof(mData[0]) * keySize * valueSize); if (mAlpha) memcpy(mAlpha, other.mAlpha, sizeof(mAlpha[0]) * keySize * valueSize); } mDataBounds = other.mDataBounds; mDataModified = true; } return *this; } /* undocumented getter */ double QCPColorMapData::data(double key, double value) { int keyCell = (key - mKeyRange.lower) / (mKeyRange.upper - mKeyRange.lower) * (mKeySize - 1) + 0.5; int valueCell = (value - mValueRange.lower) / (mValueRange.upper - mValueRange.lower) * (mValueSize - 1) + 0.5; if (keyCell >= 0 && keyCell < mKeySize && valueCell >= 0 && valueCell < mValueSize) return mData[valueCell * mKeySize + keyCell]; else return 0; } /* undocumented getter */ double QCPColorMapData::cell(int keyIndex, int valueIndex) { if (keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 && valueIndex < mValueSize) return mData[valueIndex * mKeySize + keyIndex]; else return 0; } /*! Returns the alpha map value of the cell with the indices \a keyIndex and \a valueIndex. If this color map data doesn't have an alpha map (because \ref setAlpha was never called after creation or after a call to \ref clearAlpha), returns 255, which corresponds to full opacity. \see setAlpha */ unsigned char QCPColorMapData::alpha(int keyIndex, int valueIndex) { if (mAlpha && keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 && valueIndex < mValueSize) return mAlpha[valueIndex * mKeySize + keyIndex]; else return 255; } /*! Resizes the data array to have \a keySize cells in the key dimension and \a valueSize cells in the value dimension. The current data is discarded and the map cells are set to 0, unless the map had already the requested size. Setting at least one of \a keySize or \a valueSize to zero frees the internal data array and \ref isEmpty returns true. \see setRange, setKeySize, setValueSize */ void QCPColorMapData::setSize(int keySize, int valueSize) { if (keySize != mKeySize || valueSize != mValueSize) { mKeySize = keySize; mValueSize = valueSize; if (mData) delete[] mData; mIsEmpty = mKeySize == 0 || mValueSize == 0; if (!mIsEmpty) { #ifdef __EXCEPTIONS try // 2D arrays get memory intensive fast. So if the allocation fails, at least output debug message { #endif mData = new double[mKeySize * mValueSize]; #ifdef __EXCEPTIONS } catch (...) { mData = 0; } #endif if (mData) fill(0); else qDebug() << Q_FUNC_INFO << "out of memory for data dimensions " << mKeySize << "*" << mValueSize; } else - mData = 0; + mData = nullptr; if (mAlpha) // if we had an alpha map, recreate it with new size createAlpha(); mDataModified = true; } } /*! Resizes the data array to have \a keySize cells in the key dimension. The current data is discarded and the map cells are set to 0, unless the map had already the requested size. Setting \a keySize to zero frees the internal data array and \ref isEmpty returns true. \see setKeyRange, setSize, setValueSize */ void QCPColorMapData::setKeySize(int keySize) { setSize(keySize, mValueSize); } /*! Resizes the data array to have \a valueSize cells in the value dimension. The current data is discarded and the map cells are set to 0, unless the map had already the requested size. Setting \a valueSize to zero frees the internal data array and \ref isEmpty returns true. \see setValueRange, setSize, setKeySize */ void QCPColorMapData::setValueSize(int valueSize) { setSize(mKeySize, valueSize); } /*! Sets the coordinate ranges the data shall be distributed over. This defines the rectangular area covered by the color map in plot coordinates. The outer cells will be centered on the range boundaries given to this function. For example, if the key size (\ref setKeySize) is 3 and \a keyRange is set to QCPRange(2, 3) there will be cells centered on the key coordinates 2, 2.5 and 3. \see setSize */ void QCPColorMapData::setRange(const QCPRange &keyRange, const QCPRange &valueRange) { setKeyRange(keyRange); setValueRange(valueRange); } /*! Sets the coordinate range the data shall be distributed over in the key dimension. Together with the value range, This defines the rectangular area covered by the color map in plot coordinates. The outer cells will be centered on the range boundaries given to this function. For example, if the key size (\ref setKeySize) is 3 and \a keyRange is set to QCPRange(2, 3) there will be cells centered on the key coordinates 2, 2.5 and 3. \see setRange, setValueRange, setSize */ void QCPColorMapData::setKeyRange(const QCPRange &keyRange) { mKeyRange = keyRange; } /*! Sets the coordinate range the data shall be distributed over in the value dimension. Together with the key range, This defines the rectangular area covered by the color map in plot coordinates. The outer cells will be centered on the range boundaries given to this function. For example, if the value size (\ref setValueSize) is 3 and \a valueRange is set to QCPRange(2, 3) there will be cells centered on the value coordinates 2, 2.5 and 3. \see setRange, setKeyRange, setSize */ void QCPColorMapData::setValueRange(const QCPRange &valueRange) { mValueRange = valueRange; } /*! Sets the data of the cell, which lies at the plot coordinates given by \a key and \a value, to \a z. \note The QCPColorMap always displays the data at equal key/value intervals, even if the key or value axis is set to a logarithmic scaling. If you want to use QCPColorMap with logarithmic axes, you shouldn't use the \ref QCPColorMapData::setData method as it uses a linear transformation to determine the cell index. Rather directly access the cell index with \ref QCPColorMapData::setCell. \see setCell, setRange */ void QCPColorMapData::setData(double key, double value, double z) { int keyCell = (key - mKeyRange.lower) / (mKeyRange.upper - mKeyRange.lower) * (mKeySize - 1) + 0.5; int valueCell = (value - mValueRange.lower) / (mValueRange.upper - mValueRange.lower) * (mValueSize - 1) + 0.5; if (keyCell >= 0 && keyCell < mKeySize && valueCell >= 0 && valueCell < mValueSize) { mData[valueCell * mKeySize + keyCell] = z; if (z < mDataBounds.lower) mDataBounds.lower = z; if (z > mDataBounds.upper) mDataBounds.upper = z; mDataModified = true; } } /*! Sets the data of the cell with indices \a keyIndex and \a valueIndex to \a z. The indices enumerate the cells starting from zero, up to the map's size-1 in the respective dimension (see \ref setSize). In the standard plot configuration (horizontal key axis and vertical value axis, both not range-reversed), the cell with indices (0, 0) is in the bottom left corner and the cell with indices (keySize-1, valueSize-1) is in the top right corner of the color map. \see setData, setSize */ void QCPColorMapData::setCell(int keyIndex, int valueIndex, double z) { if (keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 && valueIndex < mValueSize) { mData[valueIndex * mKeySize + keyIndex] = z; if (z < mDataBounds.lower) mDataBounds.lower = z; if (z > mDataBounds.upper) mDataBounds.upper = z; mDataModified = true; } else qDebug() << Q_FUNC_INFO << "index out of bounds:" << keyIndex << valueIndex; } /*! Sets the alpha of the color map cell given by \a keyIndex and \a valueIndex to \a alpha. A value of 0 for \a alpha results in a fully transparent cell, and a value of 255 results in a fully opaque cell. If an alpha map doesn't exist yet for this color map data, it will be created here. If you wish to restore full opacity and free any allocated memory of the alpha map, call \ref clearAlpha. Note that the cell-wise alpha which can be configured here is independent of any alpha configured in the color map's gradient (\ref QCPColorGradient). If a cell is affected both by the cell-wise and gradient alpha, the alpha values will be blended accordingly during rendering of the color map. \see fillAlpha, clearAlpha */ void QCPColorMapData::setAlpha(int keyIndex, int valueIndex, unsigned char alpha) { if (keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 && valueIndex < mValueSize) { if (mAlpha || createAlpha()) { mAlpha[valueIndex * mKeySize + keyIndex] = alpha; mDataModified = true; } } else qDebug() << Q_FUNC_INFO << "index out of bounds:" << keyIndex << valueIndex; } /*! Goes through the data and updates the buffered minimum and maximum data values. Calling this method is only advised if you are about to call \ref QCPColorMap::rescaleDataRange and can not guarantee that the cells holding the maximum or minimum data haven't been overwritten with a smaller or larger value respectively, since the buffered maximum/minimum values have been updated the last time. Why this is the case is explained in the class description (\ref QCPColorMapData). Note that the method \ref QCPColorMap::rescaleDataRange provides a parameter \a recalculateDataBounds for convenience. Setting this to true will call this method for you, before doing the rescale. */ void QCPColorMapData::recalculateDataBounds() { if (mKeySize > 0 && mValueSize > 0) { double minHeight = mData[0]; double maxHeight = mData[0]; const int dataCount = mValueSize * mKeySize; for (int i = 0; i < dataCount; ++i) { if (mData[i] > maxHeight) maxHeight = mData[i]; if (mData[i] < minHeight) minHeight = mData[i]; } mDataBounds.lower = minHeight; mDataBounds.upper = maxHeight; } } /*! Frees the internal data memory. This is equivalent to calling \ref setSize "setSize(0, 0)". */ void QCPColorMapData::clear() { setSize(0, 0); } /*! Frees the internal alpha map. The color map will have full opacity again. */ void QCPColorMapData::clearAlpha() { if (mAlpha) { delete[] mAlpha; - mAlpha = 0; + mAlpha = nullptr; mDataModified = true; } } /*! Sets all cells to the value \a z. */ void QCPColorMapData::fill(double z) { const int dataCount = mValueSize * mKeySize; for (int i = 0; i < dataCount; ++i) mData[i] = z; mDataBounds = QCPRange(z, z); mDataModified = true; } /*! Sets the opacity of all color map cells to \a alpha. A value of 0 for \a alpha results in a fully transparent color map, and a value of 255 results in a fully opaque color map. If you wish to restore opacity to 100% and free any used memory for the alpha map, rather use \ref clearAlpha. \see setAlpha */ void QCPColorMapData::fillAlpha(unsigned char alpha) { if (mAlpha || createAlpha(false)) { const int dataCount = mValueSize * mKeySize; for (int i = 0; i < dataCount; ++i) mAlpha[i] = alpha; mDataModified = true; } } /*! Transforms plot coordinates given by \a key and \a value to cell indices of this QCPColorMapData instance. The resulting cell indices are returned via the output parameters \a keyIndex and \a valueIndex. The retrieved key/value cell indices can then be used for example with \ref setCell. If you are only interested in a key or value index, you may pass 0 as \a valueIndex or \a keyIndex. \note The QCPColorMap always displays the data at equal key/value intervals, even if the key or value axis is set to a logarithmic scaling. If you want to use QCPColorMap with logarithmic axes, you shouldn't use the \ref QCPColorMapData::coordToCell method as it uses a linear transformation to determine the cell index. \see cellToCoord, QCPAxis::coordToPixel */ void QCPColorMapData::coordToCell(double key, double value, int *keyIndex, int *valueIndex) const { if (keyIndex) *keyIndex = (key - mKeyRange.lower) / (mKeyRange.upper - mKeyRange.lower) * (mKeySize - 1) + 0.5; if (valueIndex) *valueIndex = (value - mValueRange.lower) / (mValueRange.upper - mValueRange.lower) * (mValueSize - 1) + 0.5; } /*! Transforms cell indices given by \a keyIndex and \a valueIndex to cell indices of this QCPColorMapData instance. The resulting coordinates are returned via the output parameters \a key and \a value. If you are only interested in a key or value coordinate, you may pass 0 as \a key or \a value. \note The QCPColorMap always displays the data at equal key/value intervals, even if the key or value axis is set to a logarithmic scaling. If you want to use QCPColorMap with logarithmic axes, you shouldn't use the \ref QCPColorMapData::cellToCoord method as it uses a linear transformation to determine the cell index. \see coordToCell, QCPAxis::pixelToCoord */ void QCPColorMapData::cellToCoord(int keyIndex, int valueIndex, double *key, double *value) const { if (key) *key = keyIndex / (double)(mKeySize - 1) * (mKeyRange.upper - mKeyRange.lower) + mKeyRange.lower; if (value) *value = valueIndex / (double)(mValueSize - 1) * (mValueRange.upper - mValueRange.lower) + mValueRange.lower; } /*! \internal Allocates the internal alpha map with the current data map key/value size and, if \a initializeOpaque is true, initializes all values to 255. If \a initializeOpaque is false, the values are not initialized at all. In this case, the alpha map should be initialized manually, e.g. with \ref fillAlpha. If an alpha map exists already, it is deleted first. If this color map is empty (has either key or value size zero, see \ref isEmpty), the alpha map is cleared. The return value indicates the existence of the alpha map after the call. So this method returns true if the data map isn't empty and an alpha map was successfully allocated. */ bool QCPColorMapData::createAlpha(bool initializeOpaque) { clearAlpha(); if (isEmpty()) return false; #ifdef __EXCEPTIONS try // 2D arrays get memory intensive fast. So if the allocation fails, at least output debug message { #endif mAlpha = new unsigned char[mKeySize * mValueSize]; #ifdef __EXCEPTIONS } catch (...) { mAlpha = 0; } #endif if (mAlpha) { if (initializeOpaque) fillAlpha(255); return true; } else { qDebug() << Q_FUNC_INFO << "out of memory for data dimensions " << mKeySize << "*" << mValueSize; return false; } } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPColorMap //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPColorMap \brief A plottable representing a two-dimensional color map in a plot. \image html QCPColorMap.png The data is stored in the class \ref QCPColorMapData, which can be accessed via the data() method. A color map has three dimensions to represent a data point: The \a key dimension, the \a value dimension and the \a data dimension. As with other plottables such as graphs, \a key and \a value correspond to two orthogonal axes on the QCustomPlot surface that you specify in the QCPColorMap constructor. The \a data dimension however is encoded as the color of the point at (\a key, \a value). Set the number of points (or \a cells) in the key/value dimension via \ref QCPColorMapData::setSize. The plot coordinate range over which these points will be displayed is specified via \ref QCPColorMapData::setRange. The first cell will be centered on the lower range boundary and the last cell will be centered on the upper range boundary. The data can be set by either accessing the cells directly with QCPColorMapData::setCell or by addressing the cells via their plot coordinates with \ref QCPColorMapData::setData. If possible, you should prefer setCell, since it doesn't need to do any coordinate transformation and thus performs a bit better. The cell with index (0, 0) is at the bottom left, if the color map uses normal (i.e. not reversed) key and value axes. To show the user which colors correspond to which \a data values, a \ref QCPColorScale is typically placed to the right of the axis rect. See the documentation there for details on how to add and use a color scale. \section qcpcolormap-appearance Changing the appearance The central part of the appearance is the color gradient, which can be specified via \ref setGradient. See the documentation of \ref QCPColorGradient for details on configuring a color gradient. The \a data range that is mapped to the colors of the gradient can be specified with \ref setDataRange. To make the data range encompass the whole data set minimum to maximum, call \ref rescaleDataRange. \section qcpcolormap-transparency Transparency Transparency in color maps can be achieved by two mechanisms. On one hand, you can specify alpha values for color stops of the \ref QCPColorGradient, via the regular QColor interface. This will cause the color map data which gets mapped to colors around those color stops to appear with the accordingly interpolated transparency. On the other hand you can also directly apply an alpha value to each cell independent of its data, by using the alpha map feature of \ref QCPColorMapData. The relevant methods are \ref QCPColorMapData::setAlpha, QCPColorMapData::fillAlpha and \ref QCPColorMapData::clearAlpha(). The two transparencies will be joined together in the plot and otherwise not interfere with each other. They are mixed in a multiplicative matter, so an alpha of e.g. 50% (128/255) in both modes simultaneously, will result in a total transparency of 25% (64/255). \section qcpcolormap-usage Usage Like all data representing objects in QCustomPlot, the QCPColorMap is a plottable (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.) Usually, you first create an instance: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolormap-creation-1 which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead. The newly created plottable can be modified, e.g.: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolormap-creation-2 \note The QCPColorMap always displays the data at equal key/value intervals, even if the key or value axis is set to a logarithmic scaling. If you want to use QCPColorMap with logarithmic axes, you shouldn't use the \ref QCPColorMapData::setData method as it uses a linear transformation to determine the cell index. Rather directly access the cell index with \ref QCPColorMapData::setCell. */ /* start documentation of inline functions */ /*! \fn QCPColorMapData *QCPColorMap::data() const Returns a pointer to the internal data storage of type \ref QCPColorMapData. Access this to modify data points (cells) and the color map key/value range. \see setData */ /* end documentation of inline functions */ /* start documentation of signals */ /*! \fn void QCPColorMap::dataRangeChanged(const QCPRange &newRange); This signal is emitted when the data range changes. \see setDataRange */ /*! \fn void QCPColorMap::dataScaleTypeChanged(QCPAxis::ScaleType scaleType); This signal is emitted when the data scale type changes. \see setDataScaleType */ /*! \fn void QCPColorMap::gradientChanged(const QCPColorGradient &newGradient); This signal is emitted when the gradient changes. \see setGradient */ /* end documentation of signals */ /*! Constructs a color map with the specified \a keyAxis and \a valueAxis. The created QCPColorMap is automatically registered with the QCustomPlot instance inferred from \a keyAxis. This QCustomPlot instance takes ownership of the QCPColorMap, so do not delete it manually but use QCustomPlot::removePlottable() instead. */ QCPColorMap::QCPColorMap(QCPAxis *keyAxis, QCPAxis *valueAxis) : QCPAbstractPlottable(keyAxis, valueAxis), mDataScaleType(QCPAxis::stLinear), mMapData(new QCPColorMapData(10, 10, QCPRange(0, 5), QCPRange(0, 5))), mInterpolate(true), mTightBoundary(false), mMapImageInvalidated(true) { } QCPColorMap::~QCPColorMap() { delete mMapData; } /*! Replaces the current \ref data with the provided \a data. If \a copy is set to true, the \a data object will only be copied. if false, the color map takes ownership of the passed data and replaces the internal data pointer with it. This is significantly faster than copying for large datasets. */ void QCPColorMap::setData(QCPColorMapData *data, bool copy) { if (mMapData == data) { qDebug() << Q_FUNC_INFO << "The data pointer is already in (and owned by) this plottable" << reinterpret_cast(data); return; } if (copy) { *mMapData = *data; } else { delete mMapData; mMapData = data; } mMapImageInvalidated = true; } /*! Sets the data range of this color map to \a dataRange. The data range defines which data values are mapped to the color gradient. To make the data range span the full range of the data set, use \ref rescaleDataRange. \see QCPColorScale::setDataRange */ void QCPColorMap::setDataRange(const QCPRange &dataRange) { if (!QCPRange::validRange(dataRange)) return; if (mDataRange.lower != dataRange.lower || mDataRange.upper != dataRange.upper) { if (mDataScaleType == QCPAxis::stLogarithmic) mDataRange = dataRange.sanitizedForLogScale(); else mDataRange = dataRange.sanitizedForLinScale(); mMapImageInvalidated = true; emit dataRangeChanged(mDataRange); } } /*! Sets whether the data is correlated with the color gradient linearly or logarithmically. \see QCPColorScale::setDataScaleType */ void QCPColorMap::setDataScaleType(QCPAxis::ScaleType scaleType) { if (mDataScaleType != scaleType) { mDataScaleType = scaleType; mMapImageInvalidated = true; emit dataScaleTypeChanged(mDataScaleType); if (mDataScaleType == QCPAxis::stLogarithmic) setDataRange(mDataRange.sanitizedForLogScale()); } } /*! Sets the color gradient that is used to represent the data. For more details on how to create an own gradient or use one of the preset gradients, see \ref QCPColorGradient. The colors defined by the gradient will be used to represent data values in the currently set data range, see \ref setDataRange. Data points that are outside this data range will either be colored uniformly with the respective gradient boundary color, or the gradient will repeat, depending on \ref QCPColorGradient::setPeriodic. \see QCPColorScale::setGradient */ void QCPColorMap::setGradient(const QCPColorGradient &gradient) { if (mGradient != gradient) { mGradient = gradient; mMapImageInvalidated = true; emit gradientChanged(mGradient); } } /*! Sets whether the color map image shall use bicubic interpolation when displaying the color map shrinked or expanded, and not at a 1:1 pixel-to-data scale. \image html QCPColorMap-interpolate.png "A 10*10 color map, with interpolation and without interpolation enabled" */ void QCPColorMap::setInterpolate(bool enabled) { mInterpolate = enabled; mMapImageInvalidated = true; // because oversampling factors might need to change } /*! Sets whether the outer most data rows and columns are clipped to the specified key and value range (see \ref QCPColorMapData::setKeyRange, \ref QCPColorMapData::setValueRange). if \a enabled is set to false, the data points at the border of the color map are drawn with the same width and height as all other data points. Since the data points are represented by rectangles of one color centered on the data coordinate, this means that the shown color map extends by half a data point over the specified key/value range in each direction. \image html QCPColorMap-tightboundary.png "A color map, with tight boundary enabled and disabled" */ void QCPColorMap::setTightBoundary(bool enabled) { mTightBoundary = enabled; } /*! Associates the color scale \a colorScale with this color map. This means that both the color scale and the color map synchronize their gradient, data range and data scale type (\ref setGradient, \ref setDataRange, \ref setDataScaleType). Multiple color maps can be associated with one single color scale. This causes the color maps to also synchronize those properties, via the mutual color scale. This function causes the color map to adopt the current color gradient, data range and data scale type of \a colorScale. After this call, you may change these properties at either the color map or the color scale, and the setting will be applied to both. Pass 0 as \a colorScale to disconnect the color scale from this color map again. */ void QCPColorMap::setColorScale(QCPColorScale *colorScale) { if (mColorScale) // unconnect signals from old color scale { disconnect(this, SIGNAL(dataRangeChanged(QCPRange)), mColorScale.data(), SLOT(setDataRange(QCPRange))); disconnect(this, SIGNAL(dataScaleTypeChanged(QCPAxis::ScaleType)), mColorScale.data(), SLOT(setDataScaleType(QCPAxis::ScaleType))); disconnect(this, SIGNAL(gradientChanged(QCPColorGradient)), mColorScale.data(), SLOT(setGradient(QCPColorGradient))); disconnect(mColorScale.data(), SIGNAL(dataRangeChanged(QCPRange)), this, SLOT(setDataRange(QCPRange))); disconnect(mColorScale.data(), SIGNAL(gradientChanged(QCPColorGradient)), this, SLOT(setGradient(QCPColorGradient))); disconnect(mColorScale.data(), SIGNAL(dataScaleTypeChanged(QCPAxis::ScaleType)), this, SLOT(setDataScaleType(QCPAxis::ScaleType))); } mColorScale = colorScale; if (mColorScale) // connect signals to new color scale { setGradient(mColorScale.data()->gradient()); setDataRange(mColorScale.data()->dataRange()); setDataScaleType(mColorScale.data()->dataScaleType()); connect(this, SIGNAL(dataRangeChanged(QCPRange)), mColorScale.data(), SLOT(setDataRange(QCPRange))); connect(this, SIGNAL(dataScaleTypeChanged(QCPAxis::ScaleType)), mColorScale.data(), SLOT(setDataScaleType(QCPAxis::ScaleType))); connect(this, SIGNAL(gradientChanged(QCPColorGradient)), mColorScale.data(), SLOT(setGradient(QCPColorGradient))); connect(mColorScale.data(), SIGNAL(dataRangeChanged(QCPRange)), this, SLOT(setDataRange(QCPRange))); connect(mColorScale.data(), SIGNAL(gradientChanged(QCPColorGradient)), this, SLOT(setGradient(QCPColorGradient))); connect(mColorScale.data(), SIGNAL(dataScaleTypeChanged(QCPAxis::ScaleType)), this, SLOT(setDataScaleType(QCPAxis::ScaleType))); } } /*! Sets the data range (\ref setDataRange) to span the minimum and maximum values that occur in the current data set. This corresponds to the \ref rescaleKeyAxis or \ref rescaleValueAxis methods, only for the third data dimension of the color map. The minimum and maximum values of the data set are buffered in the internal QCPColorMapData instance (\ref data). As data is updated via its \ref QCPColorMapData::setCell or \ref QCPColorMapData::setData, the buffered minimum and maximum values are updated, too. For performance reasons, however, they are only updated in an expanding fashion. So the buffered maximum can only increase and the buffered minimum can only decrease. In consequence, changes to the data that actually lower the maximum of the data set (by overwriting the cell holding the current maximum with a smaller value), aren't recognized and the buffered maximum overestimates the true maximum of the data set. The same happens for the buffered minimum. To recalculate the true minimum and maximum by explicitly looking at each cell, the method QCPColorMapData::recalculateDataBounds can be used. For convenience, setting the parameter \a recalculateDataBounds calls this method before setting the data range to the buffered minimum and maximum. \see setDataRange */ void QCPColorMap::rescaleDataRange(bool recalculateDataBounds) { if (recalculateDataBounds) mMapData->recalculateDataBounds(); setDataRange(mMapData->dataBounds()); } /*! Takes the current appearance of the color map and updates the legend icon, which is used to represent this color map in the legend (see \ref QCPLegend). The \a transformMode specifies whether the rescaling is done by a faster, low quality image scaling algorithm (Qt::FastTransformation) or by a slower, higher quality algorithm (Qt::SmoothTransformation). The current color map appearance is scaled down to \a thumbSize. Ideally, this should be equal to the size of the legend icon (see \ref QCPLegend::setIconSize). If it isn't exactly the configured legend icon size, the thumb will be rescaled during drawing of the legend item. \see setDataRange */ void QCPColorMap::updateLegendIcon(Qt::TransformationMode transformMode, const QSize &thumbSize) { if (mMapImage.isNull() && !data()->isEmpty()) updateMapImage(); // try to update map image if it's null (happens if no draw has happened yet) if (!mMapImage.isNull()) // might still be null, e.g. if data is empty, so check here again { bool mirrorX = (keyAxis()->orientation() == Qt::Horizontal ? keyAxis() : valueAxis())->rangeReversed(); bool mirrorY = (valueAxis()->orientation() == Qt::Vertical ? valueAxis() : keyAxis())->rangeReversed(); mLegendIcon = QPixmap::fromImage(mMapImage.mirrored(mirrorX, mirrorY)) .scaled(thumbSize, Qt::KeepAspectRatio, transformMode); } } /* inherits documentation from base class */ double QCPColorMap::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if ((onlySelectable && mSelectable == QCP::stNone) || mMapData->isEmpty()) return -1; if (!mKeyAxis || !mValueAxis) return -1; if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint())) { double posKey, posValue; pixelsToCoords(pos, posKey, posValue); if (mMapData->keyRange().contains(posKey) && mMapData->valueRange().contains(posValue)) { if (details) details->setValue(QCPDataSelection(QCPDataRange( 0, 1))); // temporary solution, to facilitate whole-plottable selection. Replace in future version with segmented 2D selection. return mParentPlot->selectionTolerance() * 0.99; } } return -1; } /* inherits documentation from base class */ QCPRange QCPColorMap::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const { foundRange = true; QCPRange result = mMapData->keyRange(); result.normalize(); if (inSignDomain == QCP::sdPositive) { if (result.lower <= 0 && result.upper > 0) result.lower = result.upper * 1e-3; else if (result.lower <= 0 && result.upper <= 0) foundRange = false; } else if (inSignDomain == QCP::sdNegative) { if (result.upper >= 0 && result.lower < 0) result.upper = result.lower * 1e-3; else if (result.upper >= 0 && result.lower >= 0) foundRange = false; } return result; } /* inherits documentation from base class */ QCPRange QCPColorMap::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const { if (inKeyRange != QCPRange()) { if (mMapData->keyRange().upper < inKeyRange.lower || mMapData->keyRange().lower > inKeyRange.upper) { foundRange = false; return QCPRange(); } } foundRange = true; QCPRange result = mMapData->valueRange(); result.normalize(); if (inSignDomain == QCP::sdPositive) { if (result.lower <= 0 && result.upper > 0) result.lower = result.upper * 1e-3; else if (result.lower <= 0 && result.upper <= 0) foundRange = false; } else if (inSignDomain == QCP::sdNegative) { if (result.upper >= 0 && result.lower < 0) result.upper = result.lower * 1e-3; else if (result.upper >= 0 && result.lower >= 0) foundRange = false; } return result; } /*! \internal Updates the internal map image buffer by going through the internal \ref QCPColorMapData and turning the data values into color pixels with \ref QCPColorGradient::colorize. This method is called by \ref QCPColorMap::draw if either the data has been modified or the map image has been invalidated for a different reason (e.g. a change of the data range with \ref setDataRange). If the map cell count is low, the image created will be oversampled in order to avoid a QPainter::drawImage bug which makes inner pixel boundaries jitter when stretch-drawing images without smooth transform enabled. Accordingly, oversampling isn't performed if \ref setInterpolate is true. */ void QCPColorMap::updateMapImage() { QCPAxis *keyAxis = mKeyAxis.data(); if (!keyAxis) return; if (mMapData->isEmpty()) return; const QImage::Format format = QImage::Format_ARGB32_Premultiplied; const int keySize = mMapData->keySize(); const int valueSize = mMapData->valueSize(); int keyOversamplingFactor = mInterpolate ? 1 : (int)(1.0 + 100.0 / (double) keySize); // make mMapImage have at least size 100, factor becomes 1 if size > 200 or interpolation is on int valueOversamplingFactor = mInterpolate ? 1 : (int)(1.0 + 100.0 / (double) valueSize); // make mMapImage have at least size 100, factor becomes 1 if size > 200 or interpolation is on // resize mMapImage to correct dimensions including possible oversampling factors, according to key/value axes orientation: if (keyAxis->orientation() == Qt::Horizontal && (mMapImage.width() != keySize * keyOversamplingFactor || mMapImage.height() != valueSize * valueOversamplingFactor)) mMapImage = QImage(QSize(keySize * keyOversamplingFactor, valueSize * valueOversamplingFactor), format); else if (keyAxis->orientation() == Qt::Vertical && (mMapImage.width() != valueSize * valueOversamplingFactor || mMapImage.height() != keySize * keyOversamplingFactor)) mMapImage = QImage(QSize(valueSize * valueOversamplingFactor, keySize * keyOversamplingFactor), format); QImage *localMapImage = &mMapImage; // this is the image on which the colorization operates. Either the final mMapImage, or if we need oversampling, mUndersampledMapImage if (keyOversamplingFactor > 1 || valueOversamplingFactor > 1) { // resize undersampled map image to actual key/value cell sizes: if (keyAxis->orientation() == Qt::Horizontal && (mUndersampledMapImage.width() != keySize || mUndersampledMapImage.height() != valueSize)) mUndersampledMapImage = QImage(QSize(keySize, valueSize), format); else if (keyAxis->orientation() == Qt::Vertical && (mUndersampledMapImage.width() != valueSize || mUndersampledMapImage.height() != keySize)) mUndersampledMapImage = QImage(QSize(valueSize, keySize), format); localMapImage = &mUndersampledMapImage; // make the colorization run on the undersampled image } else if (!mUndersampledMapImage.isNull()) mUndersampledMapImage = QImage(); // don't need oversampling mechanism anymore (map size has changed) but mUndersampledMapImage still has nonzero size, free it const double *rawData = mMapData->mData; const unsigned char *rawAlpha = mMapData->mAlpha; if (keyAxis->orientation() == Qt::Horizontal) { const int lineCount = valueSize; const int rowCount = keySize; for (int line = 0; line < lineCount; ++line) { QRgb *pixels = reinterpret_cast(localMapImage->scanLine( lineCount - 1 - line)); // invert scanline index because QImage counts scanlines from top, but our vertical index counts from bottom (mathematical coordinate system) if (rawAlpha) mGradient.colorize(rawData + line * rowCount, rawAlpha + line * rowCount, mDataRange, pixels, rowCount, 1, mDataScaleType == QCPAxis::stLogarithmic); else mGradient.colorize(rawData + line * rowCount, mDataRange, pixels, rowCount, 1, mDataScaleType == QCPAxis::stLogarithmic); } } else // keyAxis->orientation() == Qt::Vertical { const int lineCount = keySize; const int rowCount = valueSize; for (int line = 0; line < lineCount; ++line) { QRgb *pixels = reinterpret_cast(localMapImage->scanLine( lineCount - 1 - line)); // invert scanline index because QImage counts scanlines from top, but our vertical index counts from bottom (mathematical coordinate system) if (rawAlpha) mGradient.colorize(rawData + line, rawAlpha + line, mDataRange, pixels, rowCount, lineCount, mDataScaleType == QCPAxis::stLogarithmic); else mGradient.colorize(rawData + line, mDataRange, pixels, rowCount, lineCount, mDataScaleType == QCPAxis::stLogarithmic); } } if (keyOversamplingFactor > 1 || valueOversamplingFactor > 1) { if (keyAxis->orientation() == Qt::Horizontal) mMapImage = mUndersampledMapImage.scaled(keySize * keyOversamplingFactor, valueSize * valueOversamplingFactor, Qt::IgnoreAspectRatio, Qt::FastTransformation); else mMapImage = mUndersampledMapImage.scaled(valueSize * valueOversamplingFactor, keySize * keyOversamplingFactor, Qt::IgnoreAspectRatio, Qt::FastTransformation); } mMapData->mDataModified = false; mMapImageInvalidated = false; } /* inherits documentation from base class */ void QCPColorMap::draw(QCPPainter *painter) { if (mMapData->isEmpty()) return; if (!mKeyAxis || !mValueAxis) return; applyDefaultAntialiasingHint(painter); if (mMapData->mDataModified || mMapImageInvalidated) updateMapImage(); // use buffer if painting vectorized (PDF): const bool useBuffer = painter->modes().testFlag(QCPPainter::pmVectorized); QCPPainter *localPainter = painter; // will be redirected to paint on mapBuffer if painting vectorized QRectF mapBufferTarget; // the rect in absolute widget coordinates where the visible map portion/buffer will end up in QPixmap mapBuffer; if (useBuffer) { const double mapBufferPixelRatio = 3; // factor by which DPI is increased in embedded bitmaps mapBufferTarget = painter->clipRegion().boundingRect(); mapBuffer = QPixmap((mapBufferTarget.size() * mapBufferPixelRatio).toSize()); mapBuffer.fill(Qt::transparent); localPainter = new QCPPainter(&mapBuffer); localPainter->scale(mapBufferPixelRatio, mapBufferPixelRatio); localPainter->translate(-mapBufferTarget.topLeft()); } QRectF imageRect = QRectF(coordsToPixels(mMapData->keyRange().lower, mMapData->valueRange().lower), coordsToPixels(mMapData->keyRange().upper, mMapData->valueRange().upper)) .normalized(); // extend imageRect to contain outer halves/quarters of bordering/cornering pixels (cells are centered on map range boundary): double halfCellWidth = 0; // in pixels double halfCellHeight = 0; // in pixels if (keyAxis()->orientation() == Qt::Horizontal) { if (mMapData->keySize() > 1) halfCellWidth = 0.5 * imageRect.width() / (double)(mMapData->keySize() - 1); if (mMapData->valueSize() > 1) halfCellHeight = 0.5 * imageRect.height() / (double)(mMapData->valueSize() - 1); } else // keyAxis orientation is Qt::Vertical { if (mMapData->keySize() > 1) halfCellHeight = 0.5 * imageRect.height() / (double)(mMapData->keySize() - 1); if (mMapData->valueSize() > 1) halfCellWidth = 0.5 * imageRect.width() / (double)(mMapData->valueSize() - 1); } imageRect.adjust(-halfCellWidth, -halfCellHeight, halfCellWidth, halfCellHeight); const bool mirrorX = (keyAxis()->orientation() == Qt::Horizontal ? keyAxis() : valueAxis())->rangeReversed(); const bool mirrorY = (valueAxis()->orientation() == Qt::Vertical ? valueAxis() : keyAxis())->rangeReversed(); const bool smoothBackup = localPainter->renderHints().testFlag(QPainter::SmoothPixmapTransform); localPainter->setRenderHint(QPainter::SmoothPixmapTransform, mInterpolate); QRegion clipBackup; if (mTightBoundary) { clipBackup = localPainter->clipRegion(); QRectF tightClipRect = QRectF(coordsToPixels(mMapData->keyRange().lower, mMapData->valueRange().lower), coordsToPixels(mMapData->keyRange().upper, mMapData->valueRange().upper)) .normalized(); localPainter->setClipRect(tightClipRect, Qt::IntersectClip); } localPainter->drawImage(imageRect, mMapImage.mirrored(mirrorX, mirrorY)); if (mTightBoundary) localPainter->setClipRegion(clipBackup); localPainter->setRenderHint(QPainter::SmoothPixmapTransform, smoothBackup); if (useBuffer) // localPainter painted to mapBuffer, so now draw buffer with original painter { delete localPainter; painter->drawPixmap(mapBufferTarget.toRect(), mapBuffer); } } /* inherits documentation from base class */ void QCPColorMap::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const { applyDefaultAntialiasingHint(painter); // draw map thumbnail: if (!mLegendIcon.isNull()) { QPixmap scaledIcon = mLegendIcon.scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::FastTransformation); QRectF iconRect = QRectF(0, 0, scaledIcon.width(), scaledIcon.height()); iconRect.moveCenter(rect.center()); painter->drawPixmap(iconRect.topLeft(), scaledIcon); } /* // draw frame: painter->setBrush(Qt::NoBrush); painter->setPen(Qt::black); painter->drawRect(rect.adjusted(1, 1, 0, 0)); */ } /* end of 'src/plottables/plottable-colormap.cpp' */ /* including file 'src/plottables/plottable-financial.cpp', size 42610 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPFinancialData //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPFinancialData \brief Holds the data of one single data point for QCPFinancial. The stored data is: \li \a key: coordinate on the key axis of this data point (this is the \a mainKey and the \a sortKey) \li \a open: The opening value at the data point (this is the \a mainValue) \li \a high: The high/maximum value at the data point \li \a low: The low/minimum value at the data point \li \a close: The closing value at the data point The container for storing multiple data points is \ref QCPFinancialDataContainer. It is a typedef for \ref QCPDataContainer with \ref QCPFinancialData as the DataType template parameter. See the documentation there for an explanation regarding the data type's generic methods. \see QCPFinancialDataContainer */ /* start documentation of inline functions */ /*! \fn double QCPFinancialData::sortKey() const Returns the \a key member of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn static QCPFinancialData QCPFinancialData::fromSortKey(double sortKey) Returns a data point with the specified \a sortKey. All other members are set to zero. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn static static bool QCPFinancialData::sortKeyIsMainKey() Since the member \a key is both the data point key coordinate and the data ordering parameter, this method returns true. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn double QCPFinancialData::mainKey() const Returns the \a key member of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn double QCPFinancialData::mainValue() const Returns the \a open member of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /*! \fn QCPRange QCPFinancialData::valueRange() const Returns a QCPRange spanning from the \a low to the \a high value of this data point. For a general explanation of what this method is good for in the context of the data container, see the documentation of \ref QCPDataContainer. */ /* end documentation of inline functions */ /*! Constructs a data point with key and all values set to zero. */ QCPFinancialData::QCPFinancialData() : key(0), open(0), high(0), low(0), close(0) { } /*! Constructs a data point with the specified \a key and OHLC values. */ QCPFinancialData::QCPFinancialData(double key, double open, double high, double low, double close) : key(key), open(open), high(high), low(low), close(close) { } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPFinancial //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPFinancial \brief A plottable representing a financial stock chart \image html QCPFinancial.png This plottable represents time series data binned to certain intervals, mainly used for stock charts. The two common representations OHLC (Open-High-Low-Close) bars and Candlesticks can be set via \ref setChartStyle. The data is passed via \ref setData as a set of open/high/low/close values at certain keys (typically times). This means the data must be already binned appropriately. If data is only available as a series of values (e.g. \a price against \a time), you can use the static convenience function \ref timeSeriesToOhlc to generate binned OHLC-data which can then be passed to \ref setData. The width of the OHLC bars/candlesticks can be controlled with \ref setWidth and \ref setWidthType. A typical choice is to set the width type to \ref wtPlotCoords (the default) and the width to (or slightly less than) one time bin interval width. \section qcpfinancial-appearance Changing the appearance Charts can be either single- or two-colored (\ref setTwoColored). If set to be single-colored, lines are drawn with the plottable's pen (\ref setPen) and fills with the brush (\ref setBrush). If set to two-colored, positive changes of the value during an interval (\a close >= \a open) are represented with a different pen and brush than negative changes (\a close < \a open). These can be configured with \ref setPenPositive, \ref setPenNegative, \ref setBrushPositive, and \ref setBrushNegative. In two-colored mode, the normal plottable pen/brush is ignored. Upon selection however, the normal selected pen/brush (provided by the \ref selectionDecorator) is used, irrespective of whether the chart is single- or two-colored. \section qcpfinancial-usage Usage Like all data representing objects in QCustomPlot, the QCPFinancial is a plottable (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.) Usually, you first create an instance: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpfinancial-creation-1 which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead. The newly created plottable can be modified, e.g.: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpfinancial-creation-2 Here we have used the static helper method \ref timeSeriesToOhlc, to turn a time-price data series into a 24-hour binned open-high-low-close data series as QCPFinancial uses. */ /* start of documentation of inline functions */ /*! \fn QCPFinancialDataContainer *QCPFinancial::data() const Returns a pointer to the internal data storage of type \ref QCPFinancialDataContainer. You may use it to directly manipulate the data, which may be more convenient and faster than using the regular \ref setData or \ref addData methods, in certain situations. */ /* end of documentation of inline functions */ /*! Constructs a financial chart which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have the same orientation. If either of these restrictions is violated, a corresponding message is printed to the debug output (qDebug), the construction is not aborted, though. The created QCPFinancial is automatically registered with the QCustomPlot instance inferred from \a keyAxis. This QCustomPlot instance takes ownership of the QCPFinancial, so do not delete it manually but use QCustomPlot::removePlottable() instead. */ QCPFinancial::QCPFinancial(QCPAxis *keyAxis, QCPAxis *valueAxis) : QCPAbstractPlottable1D(keyAxis, valueAxis), mChartStyle(csCandlestick), mWidth(0.5), mWidthType(wtPlotCoords), mTwoColored(true), mBrushPositive(QBrush(QColor(50, 160, 0))), mBrushNegative(QBrush(QColor(180, 0, 15))), mPenPositive(QPen(QColor(40, 150, 0))), mPenNegative(QPen(QColor(170, 5, 5))) { mSelectionDecorator->setBrush(QBrush(QColor(160, 160, 255))); } QCPFinancial::~QCPFinancial() { } /*! \overload Replaces the current data container with the provided \a data container. Since a QSharedPointer is used, multiple QCPFinancials may share the same data container safely. Modifying the data in the container will then affect all financials that share the container. Sharing can be achieved by simply exchanging the data containers wrapped in shared pointers: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpfinancial-datasharing-1 If you do not wish to share containers, but create a copy from an existing container, rather use the \ref QCPDataContainer::set method on the financial's data container directly: \snippet documentation/doc-code-snippets/mainwindow.cpp qcpfinancial-datasharing-2 \see addData, timeSeriesToOhlc */ void QCPFinancial::setData(QSharedPointer data) { mDataContainer = data; } /*! \overload Replaces the current data with the provided points in \a keys, \a open, \a high, \a low and \a close. The provided vectors should have equal length. Else, the number of added points will be the size of the smallest vector. If you can guarantee that the passed data points are sorted by \a keys in ascending order, you can set \a alreadySorted to true, to improve performance by saving a sorting run. \see addData, timeSeriesToOhlc */ void QCPFinancial::setData(const QVector &keys, const QVector &open, const QVector &high, const QVector &low, const QVector &close, bool alreadySorted) { mDataContainer->clear(); addData(keys, open, high, low, close, alreadySorted); } /*! Sets which representation style shall be used to display the OHLC data. */ void QCPFinancial::setChartStyle(QCPFinancial::ChartStyle style) { mChartStyle = style; } /*! Sets the width of the individual bars/candlesticks to \a width in plot key coordinates. A typical choice is to set it to (or slightly less than) one bin interval width. */ void QCPFinancial::setWidth(double width) { mWidth = width; } /*! Sets how the width of the financial bars is defined. See the documentation of \ref WidthType for an explanation of the possible values for \a widthType. The default value is \ref wtPlotCoords. \see setWidth */ void QCPFinancial::setWidthType(QCPFinancial::WidthType widthType) { mWidthType = widthType; } /*! Sets whether this chart shall contrast positive from negative trends per data point by using two separate colors to draw the respective bars/candlesticks. If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref setBrush). \see setPenPositive, setPenNegative, setBrushPositive, setBrushNegative */ void QCPFinancial::setTwoColored(bool twoColored) { mTwoColored = twoColored; } /*! If \ref setTwoColored is set to true, this function controls the brush that is used to draw fills of data points with a positive trend (i.e. bars/candlesticks with close >= open). If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref setBrush). \see setBrushNegative, setPenPositive, setPenNegative */ void QCPFinancial::setBrushPositive(const QBrush &brush) { mBrushPositive = brush; } /*! If \ref setTwoColored is set to true, this function controls the brush that is used to draw fills of data points with a negative trend (i.e. bars/candlesticks with close < open). If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref setBrush). \see setBrushPositive, setPenNegative, setPenPositive */ void QCPFinancial::setBrushNegative(const QBrush &brush) { mBrushNegative = brush; } /*! If \ref setTwoColored is set to true, this function controls the pen that is used to draw outlines of data points with a positive trend (i.e. bars/candlesticks with close >= open). If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref setBrush). \see setPenNegative, setBrushPositive, setBrushNegative */ void QCPFinancial::setPenPositive(const QPen &pen) { mPenPositive = pen; } /*! If \ref setTwoColored is set to true, this function controls the pen that is used to draw outlines of data points with a negative trend (i.e. bars/candlesticks with close < open). If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref setBrush). \see setPenPositive, setBrushNegative, setBrushPositive */ void QCPFinancial::setPenNegative(const QPen &pen) { mPenNegative = pen; } /*! \overload Adds the provided points in \a keys, \a open, \a high, \a low and \a close to the current data. The provided vectors should have equal length. Else, the number of added points will be the size of the smallest vector. If you can guarantee that the passed data points are sorted by \a keys in ascending order, you can set \a alreadySorted to true, to improve performance by saving a sorting run. Alternatively, you can also access and modify the data directly via the \ref data method, which returns a pointer to the internal data container. \see timeSeriesToOhlc */ void QCPFinancial::addData(const QVector &keys, const QVector &open, const QVector &high, const QVector &low, const QVector &close, bool alreadySorted) { if (keys.size() != open.size() || open.size() != high.size() || high.size() != low.size() || low.size() != close.size() || close.size() != keys.size()) qDebug() << Q_FUNC_INFO << "keys, open, high, low, close have different sizes:" << keys.size() << open.size() << high.size() << low.size() << close.size(); const int n = qMin(keys.size(), qMin(open.size(), qMin(high.size(), qMin(low.size(), close.size())))); QVector tempData(n); QVector::iterator it = tempData.begin(); const QVector::iterator itEnd = tempData.end(); int i = 0; while (it != itEnd) { it->key = keys[i]; it->open = open[i]; it->high = high[i]; it->low = low[i]; it->close = close[i]; ++it; ++i; } mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write } /*! \overload Adds the provided data point as \a key, \a open, \a high, \a low and \a close to the current data. Alternatively, you can also access and modify the data directly via the \ref data method, which returns a pointer to the internal data container. \see timeSeriesToOhlc */ void QCPFinancial::addData(double key, double open, double high, double low, double close) { mDataContainer->add(QCPFinancialData(key, open, high, low, close)); } /*! \copydoc QCPPlottableInterface1D::selectTestRect */ QCPDataSelection QCPFinancial::selectTestRect(const QRectF &rect, bool onlySelectable) const { QCPDataSelection result; if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty()) return result; if (!mKeyAxis || !mValueAxis) return result; QCPFinancialDataContainer::const_iterator visibleBegin, visibleEnd; getVisibleDataBounds(visibleBegin, visibleEnd); for (QCPFinancialDataContainer::const_iterator it = visibleBegin; it != visibleEnd; ++it) { if (rect.intersects(selectionHitBox(it))) result.addDataRange(QCPDataRange(it - mDataContainer->constBegin(), it - mDataContainer->constBegin() + 1), false); } result.simplify(); return result; } /* inherits documentation from base class */ double QCPFinancial::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty()) return -1; if (!mKeyAxis || !mValueAxis) return -1; if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint())) { // get visible data range: QCPFinancialDataContainer::const_iterator visibleBegin, visibleEnd; QCPFinancialDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd(); getVisibleDataBounds(visibleBegin, visibleEnd); // perform select test according to configured style: double result = -1; switch (mChartStyle) { case QCPFinancial::csOhlc: result = ohlcSelectTest(pos, visibleBegin, visibleEnd, closestDataPoint); break; case QCPFinancial::csCandlestick: result = candlestickSelectTest(pos, visibleBegin, visibleEnd, closestDataPoint); break; } if (details) { int pointIndex = closestDataPoint - mDataContainer->constBegin(); details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex + 1))); } return result; } return -1; } /* inherits documentation from base class */ QCPRange QCPFinancial::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const { QCPRange range = mDataContainer->keyRange(foundRange, inSignDomain); // determine exact range by including width of bars/flags: if (foundRange) { if (inSignDomain != QCP::sdPositive || range.lower - mWidth * 0.5 > 0) range.lower -= mWidth * 0.5; if (inSignDomain != QCP::sdNegative || range.upper + mWidth * 0.5 < 0) range.upper += mWidth * 0.5; } return range; } /* inherits documentation from base class */ QCPRange QCPFinancial::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const { return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange); } /*! A convenience function that converts time series data (\a value against \a time) to OHLC binned data points. The return value can then be passed on to \ref QCPFinancialDataContainer::set(const QCPFinancialDataContainer&). The size of the bins can be controlled with \a timeBinSize in the same units as \a time is given. For example, if the unit of \a time is seconds and single OHLC/Candlesticks should span an hour each, set \a timeBinSize to 3600. \a timeBinOffset allows to control precisely at what \a time coordinate a bin should start. The value passed as \a timeBinOffset doesn't need to be in the range encompassed by the \a time keys. It merely defines the mathematical offset/phase of the bins that will be used to process the data. */ QCPFinancialDataContainer QCPFinancial::timeSeriesToOhlc(const QVector &time, const QVector &value, double timeBinSize, double timeBinOffset) { QCPFinancialDataContainer data; int count = qMin(time.size(), value.size()); if (count == 0) return QCPFinancialDataContainer(); QCPFinancialData currentBinData(0, value.first(), value.first(), value.first(), value.first()); int currentBinIndex = qFloor((time.first() - timeBinOffset) / timeBinSize + 0.5); for (int i = 0; i < count; ++i) { int index = qFloor((time.at(i) - timeBinOffset) / timeBinSize + 0.5); if (currentBinIndex == index) // data point still in current bin, extend high/low: { if (value.at(i) < currentBinData.low) currentBinData.low = value.at(i); if (value.at(i) > currentBinData.high) currentBinData.high = value.at(i); if (i == count - 1) // last data point is in current bin, finalize bin: { currentBinData.close = value.at(i); currentBinData.key = timeBinOffset + (index)*timeBinSize; data.add(currentBinData); } } else // data point not anymore in current bin, set close of old and open of new bin, and add old to map: { // finalize current bin: currentBinData.close = value.at(i - 1); currentBinData.key = timeBinOffset + (index - 1) * timeBinSize; data.add(currentBinData); // start next bin: currentBinIndex = index; currentBinData.open = value.at(i); currentBinData.high = value.at(i); currentBinData.low = value.at(i); } } return data; } /* inherits documentation from base class */ void QCPFinancial::draw(QCPPainter *painter) { // get visible data range: QCPFinancialDataContainer::const_iterator visibleBegin, visibleEnd; getVisibleDataBounds(visibleBegin, visibleEnd); // loop over and draw segments of unselected/selected data: QList selectedSegments, unselectedSegments, allSegments; getDataSegments(selectedSegments, unselectedSegments); allSegments << unselectedSegments << selectedSegments; for (int i = 0; i < allSegments.size(); ++i) { bool isSelectedSegment = i >= unselectedSegments.size(); QCPFinancialDataContainer::const_iterator begin = visibleBegin; QCPFinancialDataContainer::const_iterator end = visibleEnd; mDataContainer->limitIteratorsToDataRange(begin, end, allSegments.at(i)); if (begin == end) continue; // draw data segment according to configured style: switch (mChartStyle) { case QCPFinancial::csOhlc: drawOhlcPlot(painter, begin, end, isSelectedSegment); break; case QCPFinancial::csCandlestick: drawCandlestickPlot(painter, begin, end, isSelectedSegment); break; } } // draw other selection decoration that isn't just line/scatter pens and brushes: if (mSelectionDecorator) mSelectionDecorator->drawDecoration(painter, selection()); } /* inherits documentation from base class */ void QCPFinancial::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const { painter->setAntialiasing(false); // legend icon especially of csCandlestick looks better without antialiasing if (mChartStyle == csOhlc) { if (mTwoColored) { // draw upper left half icon with positive color: painter->setBrush(mBrushPositive); painter->setPen(mPenPositive); painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint() << rect.topLeft().toPoint())); painter->drawLine( QLineF(0, rect.height() * 0.5, rect.width(), rect.height() * 0.5).translated(rect.topLeft())); painter->drawLine(QLineF(rect.width() * 0.2, rect.height() * 0.3, rect.width() * 0.2, rect.height() * 0.5) .translated(rect.topLeft())); painter->drawLine(QLineF(rect.width() * 0.8, rect.height() * 0.5, rect.width() * 0.8, rect.height() * 0.7) .translated(rect.topLeft())); // draw bottom right half icon with negative color: painter->setBrush(mBrushNegative); painter->setPen(mPenNegative); painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint() << rect.bottomRight().toPoint())); painter->drawLine( QLineF(0, rect.height() * 0.5, rect.width(), rect.height() * 0.5).translated(rect.topLeft())); painter->drawLine(QLineF(rect.width() * 0.2, rect.height() * 0.3, rect.width() * 0.2, rect.height() * 0.5) .translated(rect.topLeft())); painter->drawLine(QLineF(rect.width() * 0.8, rect.height() * 0.5, rect.width() * 0.8, rect.height() * 0.7) .translated(rect.topLeft())); } else { painter->setBrush(mBrush); painter->setPen(mPen); painter->drawLine( QLineF(0, rect.height() * 0.5, rect.width(), rect.height() * 0.5).translated(rect.topLeft())); painter->drawLine(QLineF(rect.width() * 0.2, rect.height() * 0.3, rect.width() * 0.2, rect.height() * 0.5) .translated(rect.topLeft())); painter->drawLine(QLineF(rect.width() * 0.8, rect.height() * 0.5, rect.width() * 0.8, rect.height() * 0.7) .translated(rect.topLeft())); } } else if (mChartStyle == csCandlestick) { if (mTwoColored) { // draw upper left half icon with positive color: painter->setBrush(mBrushPositive); painter->setPen(mPenPositive); painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint() << rect.topLeft().toPoint())); painter->drawLine( QLineF(0, rect.height() * 0.5, rect.width() * 0.25, rect.height() * 0.5).translated(rect.topLeft())); painter->drawLine(QLineF(rect.width() * 0.75, rect.height() * 0.5, rect.width(), rect.height() * 0.5) .translated(rect.topLeft())); painter->drawRect(QRectF(rect.width() * 0.25, rect.height() * 0.25, rect.width() * 0.5, rect.height() * 0.5) .translated(rect.topLeft())); // draw bottom right half icon with negative color: painter->setBrush(mBrushNegative); painter->setPen(mPenNegative); painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint() << rect.bottomRight().toPoint())); painter->drawLine( QLineF(0, rect.height() * 0.5, rect.width() * 0.25, rect.height() * 0.5).translated(rect.topLeft())); painter->drawLine(QLineF(rect.width() * 0.75, rect.height() * 0.5, rect.width(), rect.height() * 0.5) .translated(rect.topLeft())); painter->drawRect(QRectF(rect.width() * 0.25, rect.height() * 0.25, rect.width() * 0.5, rect.height() * 0.5) .translated(rect.topLeft())); } else { painter->setBrush(mBrush); painter->setPen(mPen); painter->drawLine( QLineF(0, rect.height() * 0.5, rect.width() * 0.25, rect.height() * 0.5).translated(rect.topLeft())); painter->drawLine(QLineF(rect.width() * 0.75, rect.height() * 0.5, rect.width(), rect.height() * 0.5) .translated(rect.topLeft())); painter->drawRect(QRectF(rect.width() * 0.25, rect.height() * 0.25, rect.width() * 0.5, rect.height() * 0.5) .translated(rect.topLeft())); } } } /*! \internal Draws the data from \a begin to \a end-1 as OHLC bars with the provided \a painter. This method is a helper function for \ref draw. It is used when the chart style is \ref csOhlc. */ void QCPFinancial::drawOhlcPlot(QCPPainter *painter, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, bool isSelected) { QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; } if (keyAxis->orientation() == Qt::Horizontal) { for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it) { if (isSelected && mSelectionDecorator) mSelectionDecorator->applyPen(painter); else if (mTwoColored) painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative); else painter->setPen(mPen); double keyPixel = keyAxis->coordToPixel(it->key); double openPixel = valueAxis->coordToPixel(it->open); double closePixel = valueAxis->coordToPixel(it->close); // draw backbone: painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it->high)), QPointF(keyPixel, valueAxis->coordToPixel(it->low))); // draw open: double pixelWidth = getPixelWidth(it->key, keyPixel); // sign of this makes sure open/close are on correct sides painter->drawLine(QPointF(keyPixel - pixelWidth, openPixel), QPointF(keyPixel, openPixel)); // draw close: painter->drawLine(QPointF(keyPixel, closePixel), QPointF(keyPixel + pixelWidth, closePixel)); } } else { for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it) { if (isSelected && mSelectionDecorator) mSelectionDecorator->applyPen(painter); else if (mTwoColored) painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative); else painter->setPen(mPen); double keyPixel = keyAxis->coordToPixel(it->key); double openPixel = valueAxis->coordToPixel(it->open); double closePixel = valueAxis->coordToPixel(it->close); // draw backbone: painter->drawLine(QPointF(valueAxis->coordToPixel(it->high), keyPixel), QPointF(valueAxis->coordToPixel(it->low), keyPixel)); // draw open: double pixelWidth = getPixelWidth(it->key, keyPixel); // sign of this makes sure open/close are on correct sides painter->drawLine(QPointF(openPixel, keyPixel - pixelWidth), QPointF(openPixel, keyPixel)); // draw close: painter->drawLine(QPointF(closePixel, keyPixel), QPointF(closePixel, keyPixel + pixelWidth)); } } } /*! \internal Draws the data from \a begin to \a end-1 as Candlesticks with the provided \a painter. This method is a helper function for \ref draw. It is used when the chart style is \ref csCandlestick. */ void QCPFinancial::drawCandlestickPlot(QCPPainter *painter, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, bool isSelected) { QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; } if (keyAxis->orientation() == Qt::Horizontal) { for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it) { if (isSelected && mSelectionDecorator) { mSelectionDecorator->applyPen(painter); mSelectionDecorator->applyBrush(painter); } else if (mTwoColored) { painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative); painter->setBrush(it->close >= it->open ? mBrushPositive : mBrushNegative); } else { painter->setPen(mPen); painter->setBrush(mBrush); } double keyPixel = keyAxis->coordToPixel(it->key); double openPixel = valueAxis->coordToPixel(it->open); double closePixel = valueAxis->coordToPixel(it->close); // draw high: painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it->high)), QPointF(keyPixel, valueAxis->coordToPixel(qMax(it->open, it->close)))); // draw low: painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it->low)), QPointF(keyPixel, valueAxis->coordToPixel(qMin(it->open, it->close)))); // draw open-close box: double pixelWidth = getPixelWidth(it->key, keyPixel); painter->drawRect( QRectF(QPointF(keyPixel - pixelWidth, closePixel), QPointF(keyPixel + pixelWidth, openPixel))); } } else // keyAxis->orientation() == Qt::Vertical { for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it) { if (isSelected && mSelectionDecorator) { mSelectionDecorator->applyPen(painter); mSelectionDecorator->applyBrush(painter); } else if (mTwoColored) { painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative); painter->setBrush(it->close >= it->open ? mBrushPositive : mBrushNegative); } else { painter->setPen(mPen); painter->setBrush(mBrush); } double keyPixel = keyAxis->coordToPixel(it->key); double openPixel = valueAxis->coordToPixel(it->open); double closePixel = valueAxis->coordToPixel(it->close); // draw high: painter->drawLine(QPointF(valueAxis->coordToPixel(it->high), keyPixel), QPointF(valueAxis->coordToPixel(qMax(it->open, it->close)), keyPixel)); // draw low: painter->drawLine(QPointF(valueAxis->coordToPixel(it->low), keyPixel), QPointF(valueAxis->coordToPixel(qMin(it->open, it->close)), keyPixel)); // draw open-close box: double pixelWidth = getPixelWidth(it->key, keyPixel); painter->drawRect( QRectF(QPointF(closePixel, keyPixel - pixelWidth), QPointF(openPixel, keyPixel + pixelWidth))); } } } /*! \internal This function is used to determine the width of the bar at coordinate \a key, according to the specified width (\ref setWidth) and width type (\ref setWidthType). Provide the pixel position of \a key in \a keyPixel (because usually this was already calculated via \ref QCPAxis::coordToPixel when this function is called). It returns the number of pixels the bar extends to higher keys, relative to the \a key coordinate. So with a non-reversed horizontal axis, the return value is positive. With a reversed horizontal axis, the return value is negative. This is important so the open/close flags on the \ref csOhlc bar are drawn to the correct side. */ double QCPFinancial::getPixelWidth(double key, double keyPixel) const { double result = 0; switch (mWidthType) { case wtAbsolute: { if (mKeyAxis) result = mWidth * 0.5 * mKeyAxis.data()->pixelOrientation(); break; } case wtAxisRectRatio: { if (mKeyAxis && mKeyAxis.data()->axisRect()) { if (mKeyAxis.data()->orientation() == Qt::Horizontal) result = mKeyAxis.data()->axisRect()->width() * mWidth * 0.5 * mKeyAxis.data()->pixelOrientation(); else result = mKeyAxis.data()->axisRect()->height() * mWidth * 0.5 * mKeyAxis.data()->pixelOrientation(); } else qDebug() << Q_FUNC_INFO << "No key axis or axis rect defined"; break; } case wtPlotCoords: { if (mKeyAxis) result = mKeyAxis.data()->coordToPixel(key + mWidth * 0.5) - keyPixel; else qDebug() << Q_FUNC_INFO << "No key axis defined"; break; } } return result; } /*! \internal This method is a helper function for \ref selectTest. It is used to test for selection when the chart style is \ref csOhlc. It only tests against the data points between \a begin and \a end. Like \ref selectTest, this method returns the shortest distance of \a pos to the graphical representation of the plottable, and \a closestDataPoint will point to the respective data point. */ double QCPFinancial::ohlcSelectTest(const QPointF &pos, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, QCPFinancialDataContainer::const_iterator &closestDataPoint) const { closestDataPoint = mDataContainer->constEnd(); QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return -1; } double minDistSqr = std::numeric_limits::max(); if (keyAxis->orientation() == Qt::Horizontal) { for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it) { double keyPixel = keyAxis->coordToPixel(it->key); // calculate distance to backbone: double currentDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(keyPixel, valueAxis->coordToPixel(it->high)), QCPVector2D(keyPixel, valueAxis->coordToPixel(it->low))); if (currentDistSqr < minDistSqr) { minDistSqr = currentDistSqr; closestDataPoint = it; } } } else // keyAxis->orientation() == Qt::Vertical { for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it) { double keyPixel = keyAxis->coordToPixel(it->key); // calculate distance to backbone: double currentDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(valueAxis->coordToPixel(it->high), keyPixel), QCPVector2D(valueAxis->coordToPixel(it->low), keyPixel)); if (currentDistSqr < minDistSqr) { minDistSqr = currentDistSqr; closestDataPoint = it; } } } return qSqrt(minDistSqr); } /*! \internal This method is a helper function for \ref selectTest. It is used to test for selection when the chart style is \ref csCandlestick. It only tests against the data points between \a begin and \a end. Like \ref selectTest, this method returns the shortest distance of \a pos to the graphical representation of the plottable, and \a closestDataPoint will point to the respective data point. */ double QCPFinancial::candlestickSelectTest(const QPointF &pos, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, QCPFinancialDataContainer::const_iterator &closestDataPoint) const { closestDataPoint = mDataContainer->constEnd(); QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return -1; } double minDistSqr = std::numeric_limits::max(); if (keyAxis->orientation() == Qt::Horizontal) { for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it) { double currentDistSqr; // determine whether pos is in open-close-box: QCPRange boxKeyRange(it->key - mWidth * 0.5, it->key + mWidth * 0.5); QCPRange boxValueRange(it->close, it->open); double posKey, posValue; pixelsToCoords(pos, posKey, posValue); if (boxKeyRange.contains(posKey) && boxValueRange.contains(posValue)) // is in open-close-box { currentDistSqr = mParentPlot->selectionTolerance() * 0.99 * mParentPlot->selectionTolerance() * 0.99; } else { // calculate distance to high/low lines: double keyPixel = keyAxis->coordToPixel(it->key); double highLineDistSqr = QCPVector2D(pos).distanceSquaredToLine( QCPVector2D(keyPixel, valueAxis->coordToPixel(it->high)), QCPVector2D(keyPixel, valueAxis->coordToPixel(qMax(it->open, it->close)))); double lowLineDistSqr = QCPVector2D(pos).distanceSquaredToLine( QCPVector2D(keyPixel, valueAxis->coordToPixel(it->low)), QCPVector2D(keyPixel, valueAxis->coordToPixel(qMin(it->open, it->close)))); currentDistSqr = qMin(highLineDistSqr, lowLineDistSqr); } if (currentDistSqr < minDistSqr) { minDistSqr = currentDistSqr; closestDataPoint = it; } } } else // keyAxis->orientation() == Qt::Vertical { for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it) { double currentDistSqr; // determine whether pos is in open-close-box: QCPRange boxKeyRange(it->key - mWidth * 0.5, it->key + mWidth * 0.5); QCPRange boxValueRange(it->close, it->open); double posKey, posValue; pixelsToCoords(pos, posKey, posValue); if (boxKeyRange.contains(posKey) && boxValueRange.contains(posValue)) // is in open-close-box { currentDistSqr = mParentPlot->selectionTolerance() * 0.99 * mParentPlot->selectionTolerance() * 0.99; } else { // calculate distance to high/low lines: double keyPixel = keyAxis->coordToPixel(it->key); double highLineDistSqr = QCPVector2D(pos).distanceSquaredToLine( QCPVector2D(valueAxis->coordToPixel(it->high), keyPixel), QCPVector2D(valueAxis->coordToPixel(qMax(it->open, it->close)), keyPixel)); double lowLineDistSqr = QCPVector2D(pos).distanceSquaredToLine( QCPVector2D(valueAxis->coordToPixel(it->low), keyPixel), QCPVector2D(valueAxis->coordToPixel(qMin(it->open, it->close)), keyPixel)); currentDistSqr = qMin(highLineDistSqr, lowLineDistSqr); } if (currentDistSqr < minDistSqr) { minDistSqr = currentDistSqr; closestDataPoint = it; } } } return qSqrt(minDistSqr); } /*! \internal called by the drawing methods to determine which data (key) range is visible at the current key axis range setting, so only that needs to be processed. \a begin returns an iterator to the lowest data point that needs to be taken into account when plotting. Note that in order to get a clean plot all the way to the edge of the axis rect, \a begin may still be just outside the visible range. \a end returns the iterator just above the highest data point that needs to be taken into account. Same as before, \a end may also lie just outside of the visible range if the plottable contains no data, both \a begin and \a end point to \c constEnd. */ void QCPFinancial::getVisibleDataBounds(QCPFinancialDataContainer::const_iterator &begin, QCPFinancialDataContainer::const_iterator &end) const { if (!mKeyAxis) { qDebug() << Q_FUNC_INFO << "invalid key axis"; begin = mDataContainer->constEnd(); end = mDataContainer->constEnd(); return; } begin = mDataContainer->findBegin( mKeyAxis.data()->range().lower - mWidth * 0.5); // subtract half width of ohlc/candlestick to include partially visible data points end = mDataContainer->findEnd( mKeyAxis.data()->range().upper + mWidth * 0.5); // add half width of ohlc/candlestick to include partially visible data points } /*! \internal Returns the hit box in pixel coordinates that will be used for data selection with the selection rect (\ref selectTestRect), of the data point given by \a it. */ QRectF QCPFinancial::selectionHitBox(QCPFinancialDataContainer::const_iterator it) const { QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QRectF(); } double keyPixel = keyAxis->coordToPixel(it->key); double highPixel = valueAxis->coordToPixel(it->high); double lowPixel = valueAxis->coordToPixel(it->low); double keyWidthPixels = keyPixel - keyAxis->coordToPixel(it->key - mWidth * 0.5); if (keyAxis->orientation() == Qt::Horizontal) return QRectF(keyPixel - keyWidthPixels, highPixel, keyWidthPixels * 2, lowPixel - highPixel).normalized(); else return QRectF(highPixel, keyPixel - keyWidthPixels, lowPixel - highPixel, keyWidthPixels * 2).normalized(); } /* end of 'src/plottables/plottable-financial.cpp' */ /* including file 'src/plottables/plottable-errorbar.cpp', size 37210 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPErrorBarsData //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPErrorBarsData \brief Holds the data of one single error bar for QCPErrorBars. The stored data is: \li \a errorMinus: how much the error bar extends towards negative coordinates from the data point position \li \a errorPlus: how much the error bar extends towards positive coordinates from the data point position The container for storing the error bar information is \ref QCPErrorBarsDataContainer. It is a typedef for QVector<\ref QCPErrorBarsData>. \see QCPErrorBarsDataContainer */ /*! Constructs an error bar with errors set to zero. */ QCPErrorBarsData::QCPErrorBarsData() : errorMinus(0), errorPlus(0) { } /*! Constructs an error bar with equal \a error in both negative and positive direction. */ QCPErrorBarsData::QCPErrorBarsData(double error) : errorMinus(error), errorPlus(error) { } /*! Constructs an error bar with negative and positive errors set to \a errorMinus and \a errorPlus, respectively. */ QCPErrorBarsData::QCPErrorBarsData(double errorMinus, double errorPlus) : errorMinus(errorMinus), errorPlus(errorPlus) { } //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPErrorBars //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPErrorBars \brief A plottable that adds a set of error bars to other plottables. \image html QCPErrorBars.png The \ref QCPErrorBars plottable can be attached to other one-dimensional plottables (e.g. \ref QCPGraph, \ref QCPCurve, \ref QCPBars, etc.) and equips them with error bars. Use \ref setDataPlottable to define for which plottable the \ref QCPErrorBars shall display the error bars. The orientation of the error bars can be controlled with \ref setErrorType. By using \ref setData, you can supply the actual error data, either as symmetric error or plus/minus asymmetric errors. \ref QCPErrorBars only stores the error data. The absolute key/value position of each error bar will be adopted from the configured data plottable. The error data of the \ref QCPErrorBars are associated one-to-one via their index to the data points of the data plottable. You can directly access and manipulate the error bar data via \ref data. Set either of the plus/minus errors to NaN (qQNaN() or std::numeric_limits::quiet_NaN()) to not show the respective error bar on the data point at that index. \section qcperrorbars-appearance Changing the appearance The appearance of the error bars is defined by the pen (\ref setPen), and the width of the whiskers (\ref setWhiskerWidth). Further, the error bar backbones may leave a gap around the data point center to prevent that error bars are drawn too close to or even through scatter points. This gap size can be controlled via \ref setSymbolGap. */ /* start of documentation of inline functions */ /*! \fn QSharedPointer QCPErrorBars::data() const Returns a shared pointer to the internal data storage of type \ref QCPErrorBarsDataContainer. You may use it to directly manipulate the error values, which may be more convenient and faster than using the regular \ref setData methods. */ /* end of documentation of inline functions */ /*! Constructs an error bars plottable which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have the same orientation. If either of these restrictions is violated, a corresponding message is printed to the debug output (qDebug), the construction is not aborted, though. It is also important that the \a keyAxis and \a valueAxis are the same for the error bars plottable and the data plottable that the error bars shall be drawn on (\ref setDataPlottable). The created \ref QCPErrorBars is automatically registered with the QCustomPlot instance inferred from \a keyAxis. This QCustomPlot instance takes ownership of the \ref QCPErrorBars, so do not delete it manually but use \ref QCustomPlot::removePlottable() instead. */ QCPErrorBars::QCPErrorBars(QCPAxis *keyAxis, QCPAxis *valueAxis) : QCPAbstractPlottable(keyAxis, valueAxis), mDataContainer(new QVector), mErrorType(etValueError), mWhiskerWidth(9), mSymbolGap(10) { setPen(QPen(Qt::black, 0)); setBrush(Qt::NoBrush); } QCPErrorBars::~QCPErrorBars() { } /*! \overload Replaces the current data container with the provided \a data container. Since a QSharedPointer is used, multiple \ref QCPErrorBars instances may share the same data container safely. Modifying the data in the container will then affect all \ref QCPErrorBars instances that share the container. Sharing can be achieved by simply exchanging the data containers wrapped in shared pointers: \snippet documentation/doc-code-snippets/mainwindow.cpp qcperrorbars-datasharing-1 If you do not wish to share containers, but create a copy from an existing container, assign the data containers directly: \snippet documentation/doc-code-snippets/mainwindow.cpp qcperrorbars-datasharing-2 (This uses different notation compared with other plottables, because the \ref QCPErrorBars uses a \c QVector as its data container, instead of a \ref QCPDataContainer.) \see addData */ void QCPErrorBars::setData(QSharedPointer data) { mDataContainer = data; } /*! \overload Sets symmetrical error values as specified in \a error. The errors will be associated one-to-one by the data point index to the associated data plottable (\ref setDataPlottable). You can directly access and manipulate the error bar data via \ref data. \see addData */ void QCPErrorBars::setData(const QVector &error) { mDataContainer->clear(); addData(error); } /*! \overload Sets asymmetrical errors as specified in \a errorMinus and \a errorPlus. The errors will be associated one-to-one by the data point index to the associated data plottable (\ref setDataPlottable). You can directly access and manipulate the error bar data via \ref data. \see addData */ void QCPErrorBars::setData(const QVector &errorMinus, const QVector &errorPlus) { mDataContainer->clear(); addData(errorMinus, errorPlus); } /*! Sets the data plottable to which the error bars will be applied. The error values specified e.g. via \ref setData will be associated one-to-one by the data point index to the data points of \a plottable. This means that the error bars will adopt the key/value coordinates of the data point with the same index. The passed \a plottable must be a one-dimensional plottable, i.e. it must implement the \ref QCPPlottableInterface1D. Further, it must not be a \ref QCPErrorBars instance itself. If either of these restrictions is violated, a corresponding qDebug output is generated, and the data plottable of this \ref QCPErrorBars instance is set to zero. For proper display, care must also be taken that the key and value axes of the \a plottable match those configured for this \ref QCPErrorBars instance. */ void QCPErrorBars::setDataPlottable(QCPAbstractPlottable *plottable) { if (plottable && qobject_cast(plottable)) { - mDataPlottable = 0; + mDataPlottable = nullptr; qDebug() << Q_FUNC_INFO << "can't set another QCPErrorBars instance as data plottable"; return; } if (plottable && !plottable->interface1D()) { - mDataPlottable = 0; + mDataPlottable = nullptr; qDebug() << Q_FUNC_INFO << "passed plottable doesn't implement 1d interface, can't associate with QCPErrorBars"; return; } mDataPlottable = plottable; } /*! Sets in which orientation the error bars shall appear on the data points. If your data needs both error dimensions, create two \ref QCPErrorBars with different \a type. */ void QCPErrorBars::setErrorType(ErrorType type) { mErrorType = type; } /*! Sets the width of the whiskers (the short bars at the end of the actual error bar backbones) to \a pixels. */ void QCPErrorBars::setWhiskerWidth(double pixels) { mWhiskerWidth = pixels; } /*! Sets the gap diameter around the data points that will be left out when drawing the error bar backbones. This gap prevents that error bars are drawn too close to or even through scatter points. */ void QCPErrorBars::setSymbolGap(double pixels) { mSymbolGap = pixels; } /*! \overload Adds symmetrical error values as specified in \a error. The errors will be associated one-to-one by the data point index to the associated data plottable (\ref setDataPlottable). You can directly access and manipulate the error bar data via \ref data. \see setData */ void QCPErrorBars::addData(const QVector &error) { addData(error, error); } /*! \overload Adds asymmetrical errors as specified in \a errorMinus and \a errorPlus. The errors will be associated one-to-one by the data point index to the associated data plottable (\ref setDataPlottable). You can directly access and manipulate the error bar data via \ref data. \see setData */ void QCPErrorBars::addData(const QVector &errorMinus, const QVector &errorPlus) { if (errorMinus.size() != errorPlus.size()) qDebug() << Q_FUNC_INFO << "minus and plus error vectors have different sizes:" << errorMinus.size() << errorPlus.size(); const int n = qMin(errorMinus.size(), errorPlus.size()); mDataContainer->reserve(n); for (int i = 0; i < n; ++i) mDataContainer->append(QCPErrorBarsData(errorMinus.at(i), errorPlus.at(i))); } /*! \overload Adds a single symmetrical error bar as specified in \a error. The errors will be associated one-to-one by the data point index to the associated data plottable (\ref setDataPlottable). You can directly access and manipulate the error bar data via \ref data. \see setData */ void QCPErrorBars::addData(double error) { mDataContainer->append(QCPErrorBarsData(error)); } /*! \overload Adds a single asymmetrical error bar as specified in \a errorMinus and \a errorPlus. The errors will be associated one-to-one by the data point index to the associated data plottable (\ref setDataPlottable). You can directly access and manipulate the error bar data via \ref data. \see setData */ void QCPErrorBars::addData(double errorMinus, double errorPlus) { mDataContainer->append(QCPErrorBarsData(errorMinus, errorPlus)); } /* inherits documentation from base class */ int QCPErrorBars::dataCount() const { return mDataContainer->size(); } /* inherits documentation from base class */ double QCPErrorBars::dataMainKey(int index) const { if (mDataPlottable) return mDataPlottable->interface1D()->dataMainKey(index); else qDebug() << Q_FUNC_INFO << "no data plottable set"; return 0; } /* inherits documentation from base class */ double QCPErrorBars::dataSortKey(int index) const { if (mDataPlottable) return mDataPlottable->interface1D()->dataSortKey(index); else qDebug() << Q_FUNC_INFO << "no data plottable set"; return 0; } /* inherits documentation from base class */ double QCPErrorBars::dataMainValue(int index) const { if (mDataPlottable) return mDataPlottable->interface1D()->dataMainValue(index); else qDebug() << Q_FUNC_INFO << "no data plottable set"; return 0; } /* inherits documentation from base class */ QCPRange QCPErrorBars::dataValueRange(int index) const { if (mDataPlottable) { const double value = mDataPlottable->interface1D()->dataMainValue(index); if (index >= 0 && index < mDataContainer->size() && mErrorType == etValueError) return QCPRange(value - mDataContainer->at(index).errorMinus, value + mDataContainer->at(index).errorPlus); else return QCPRange(value, value); } else { qDebug() << Q_FUNC_INFO << "no data plottable set"; return QCPRange(); } } /* inherits documentation from base class */ QPointF QCPErrorBars::dataPixelPosition(int index) const { if (mDataPlottable) return mDataPlottable->interface1D()->dataPixelPosition(index); else qDebug() << Q_FUNC_INFO << "no data plottable set"; return QPointF(); } /* inherits documentation from base class */ bool QCPErrorBars::sortKeyIsMainKey() const { if (mDataPlottable) { return mDataPlottable->interface1D()->sortKeyIsMainKey(); } else { qDebug() << Q_FUNC_INFO << "no data plottable set"; return true; } } /*! \copydoc QCPPlottableInterface1D::selectTestRect */ QCPDataSelection QCPErrorBars::selectTestRect(const QRectF &rect, bool onlySelectable) const { QCPDataSelection result; if (!mDataPlottable) return result; if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty()) return result; if (!mKeyAxis || !mValueAxis) return result; QCPErrorBarsDataContainer::const_iterator visibleBegin, visibleEnd; getVisibleDataBounds(visibleBegin, visibleEnd, QCPDataRange(0, dataCount())); QVector backbones, whiskers; for (QCPErrorBarsDataContainer::const_iterator it = visibleBegin; it != visibleEnd; ++it) { backbones.clear(); whiskers.clear(); getErrorBarLines(it, backbones, whiskers); for (int i = 0; i < backbones.size(); ++i) { if (rectIntersectsLine(rect, backbones.at(i))) { result.addDataRange( QCPDataRange(it - mDataContainer->constBegin(), it - mDataContainer->constBegin() + 1), false); break; } } } result.simplify(); return result; } /* inherits documentation from base class */ int QCPErrorBars::findBegin(double sortKey, bool expandedRange) const { if (mDataPlottable) { if (mDataContainer->isEmpty()) return 0; int beginIndex = mDataPlottable->interface1D()->findBegin(sortKey, expandedRange); if (beginIndex >= mDataContainer->size()) beginIndex = mDataContainer->size() - 1; return beginIndex; } else qDebug() << Q_FUNC_INFO << "no data plottable set"; return 0; } /* inherits documentation from base class */ int QCPErrorBars::findEnd(double sortKey, bool expandedRange) const { if (mDataPlottable) { if (mDataContainer->isEmpty()) return 0; int endIndex = mDataPlottable->interface1D()->findEnd(sortKey, expandedRange); if (endIndex > mDataContainer->size()) endIndex = mDataContainer->size(); return endIndex; } else qDebug() << Q_FUNC_INFO << "no data plottable set"; return 0; } /* inherits documentation from base class */ double QCPErrorBars::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { if (!mDataPlottable) return -1; if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty()) return -1; if (!mKeyAxis || !mValueAxis) return -1; if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint())) { QCPErrorBarsDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd(); double result = pointDistance(pos, closestDataPoint); if (details) { int pointIndex = closestDataPoint - mDataContainer->constBegin(); details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex + 1))); } return result; } else return -1; } /* inherits documentation from base class */ void QCPErrorBars::draw(QCPPainter *painter) { if (!mDataPlottable) return; if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; } if (mKeyAxis.data()->range().size() <= 0 || mDataContainer->isEmpty()) return; // if the sort key isn't the main key, we must check the visibility for each data point/error bar individually // (getVisibleDataBounds applies range restriction, but otherwise can only return full data range): bool checkPointVisibility = !mDataPlottable->interface1D()->sortKeyIsMainKey(); // check data validity if flag set: #ifdef QCUSTOMPLOT_CHECK_DATA QCPErrorBarsDataContainer::const_iterator it; for (it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it) { if (QCP::isInvalidData(it->errorMinus, it->errorPlus)) qDebug() << Q_FUNC_INFO << "Data point at index" << it - mDataContainer->constBegin() << "invalid." << "Plottable name:" << name(); } #endif applyDefaultAntialiasingHint(painter); painter->setBrush(Qt::NoBrush); // loop over and draw segments of unselected/selected data: QList selectedSegments, unselectedSegments, allSegments; getDataSegments(selectedSegments, unselectedSegments); allSegments << unselectedSegments << selectedSegments; QVector backbones, whiskers; for (int i = 0; i < allSegments.size(); ++i) { QCPErrorBarsDataContainer::const_iterator begin, end; getVisibleDataBounds(begin, end, allSegments.at(i)); if (begin == end) continue; bool isSelectedSegment = i >= unselectedSegments.size(); if (isSelectedSegment && mSelectionDecorator) mSelectionDecorator->applyPen(painter); else painter->setPen(mPen); if (painter->pen().capStyle() == Qt::SquareCap) { QPen capFixPen(painter->pen()); capFixPen.setCapStyle(Qt::FlatCap); painter->setPen(capFixPen); } backbones.clear(); whiskers.clear(); for (QCPErrorBarsDataContainer::const_iterator it = begin; it != end; ++it) { if (!checkPointVisibility || errorBarVisible(it - mDataContainer->constBegin())) getErrorBarLines(it, backbones, whiskers); } painter->drawLines(backbones); painter->drawLines(whiskers); } // draw other selection decoration that isn't just line/scatter pens and brushes: if (mSelectionDecorator) mSelectionDecorator->drawDecoration(painter, selection()); } /* inherits documentation from base class */ void QCPErrorBars::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const { applyDefaultAntialiasingHint(painter); painter->setPen(mPen); if (mErrorType == etValueError && mValueAxis && mValueAxis->orientation() == Qt::Vertical) { painter->drawLine(QLineF(rect.center().x(), rect.top() + 2, rect.center().x(), rect.bottom() - 1)); painter->drawLine(QLineF(rect.center().x() - 4, rect.top() + 2, rect.center().x() + 4, rect.top() + 2)); painter->drawLine(QLineF(rect.center().x() - 4, rect.bottom() - 1, rect.center().x() + 4, rect.bottom() - 1)); } else { painter->drawLine(QLineF(rect.left() + 2, rect.center().y(), rect.right() - 2, rect.center().y())); painter->drawLine(QLineF(rect.left() + 2, rect.center().y() - 4, rect.left() + 2, rect.center().y() + 4)); painter->drawLine(QLineF(rect.right() - 2, rect.center().y() - 4, rect.right() - 2, rect.center().y() + 4)); } } /* inherits documentation from base class */ QCPRange QCPErrorBars::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const { if (!mDataPlottable) { foundRange = false; return QCPRange(); } QCPRange range; bool haveLower = false; bool haveUpper = false; QCPErrorBarsDataContainer::const_iterator it; for (it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it) { if (mErrorType == etValueError) { // error bar doesn't extend in key dimension (except whisker but we ignore that here), so only use data point center const double current = mDataPlottable->interface1D()->dataMainKey(it - mDataContainer->constBegin()); if (qIsNaN(current)) continue; if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0)) { if (current < range.lower || !haveLower) { range.lower = current; haveLower = true; } if (current > range.upper || !haveUpper) { range.upper = current; haveUpper = true; } } } else // mErrorType == etKeyError { const double dataKey = mDataPlottable->interface1D()->dataMainKey(it - mDataContainer->constBegin()); if (qIsNaN(dataKey)) continue; // plus error: double current = dataKey + (qIsNaN(it->errorPlus) ? 0 : it->errorPlus); if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0)) { if (current > range.upper || !haveUpper) { range.upper = current; haveUpper = true; } } // minus error: current = dataKey - (qIsNaN(it->errorMinus) ? 0 : it->errorMinus); if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0)) { if (current < range.lower || !haveLower) { range.lower = current; haveLower = true; } } } } if (haveUpper && !haveLower) { range.lower = range.upper; haveLower = true; } else if (haveLower && !haveUpper) { range.upper = range.lower; haveUpper = true; } foundRange = haveLower && haveUpper; return range; } /* inherits documentation from base class */ QCPRange QCPErrorBars::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const { if (!mDataPlottable) { foundRange = false; return QCPRange(); } QCPRange range; const bool restrictKeyRange = inKeyRange != QCPRange(); bool haveLower = false; bool haveUpper = false; QCPErrorBarsDataContainer::const_iterator itBegin = mDataContainer->constBegin(); QCPErrorBarsDataContainer::const_iterator itEnd = mDataContainer->constEnd(); if (mDataPlottable->interface1D()->sortKeyIsMainKey() && restrictKeyRange) { itBegin = mDataContainer->constBegin() + findBegin(inKeyRange.lower); itEnd = mDataContainer->constBegin() + findEnd(inKeyRange.upper); } for (QCPErrorBarsDataContainer::const_iterator it = itBegin; it != itEnd; ++it) { if (restrictKeyRange) { const double dataKey = mDataPlottable->interface1D()->dataMainKey(it - mDataContainer->constBegin()); if (dataKey < inKeyRange.lower || dataKey > inKeyRange.upper) continue; } if (mErrorType == etValueError) { const double dataValue = mDataPlottable->interface1D()->dataMainValue(it - mDataContainer->constBegin()); if (qIsNaN(dataValue)) continue; // plus error: double current = dataValue + (qIsNaN(it->errorPlus) ? 0 : it->errorPlus); if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0)) { if (current > range.upper || !haveUpper) { range.upper = current; haveUpper = true; } } // minus error: current = dataValue - (qIsNaN(it->errorMinus) ? 0 : it->errorMinus); if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0)) { if (current < range.lower || !haveLower) { range.lower = current; haveLower = true; } } } else // mErrorType == etKeyError { // error bar doesn't extend in value dimension (except whisker but we ignore that here), so only use data point center const double current = mDataPlottable->interface1D()->dataMainValue(it - mDataContainer->constBegin()); if (qIsNaN(current)) continue; if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0)) { if (current < range.lower || !haveLower) { range.lower = current; haveLower = true; } if (current > range.upper || !haveUpper) { range.upper = current; haveUpper = true; } } } } if (haveUpper && !haveLower) { range.lower = range.upper; haveLower = true; } else if (haveLower && !haveUpper) { range.upper = range.lower; haveUpper = true; } foundRange = haveLower && haveUpper; return range; } /*! \internal Calculates the lines that make up the error bar belonging to the data point \a it. The resulting lines are added to \a backbones and \a whiskers. The vectors are not cleared, so calling this method with different \a it but the same \a backbones and \a whiskers allows to accumulate lines for multiple data points. This method assumes that \a it is a valid iterator within the bounds of this \ref QCPErrorBars instance and within the bounds of the associated data plottable. */ void QCPErrorBars::getErrorBarLines(QCPErrorBarsDataContainer::const_iterator it, QVector &backbones, QVector &whiskers) const { if (!mDataPlottable) return; int index = it - mDataContainer->constBegin(); QPointF centerPixel = mDataPlottable->interface1D()->dataPixelPosition(index); if (qIsNaN(centerPixel.x()) || qIsNaN(centerPixel.y())) return; QCPAxis *errorAxis = mErrorType == etValueError ? mValueAxis : mKeyAxis; QCPAxis *orthoAxis = mErrorType == etValueError ? mKeyAxis : mValueAxis; const double centerErrorAxisPixel = errorAxis->orientation() == Qt::Horizontal ? centerPixel.x() : centerPixel.y(); const double centerOrthoAxisPixel = orthoAxis->orientation() == Qt::Horizontal ? centerPixel.x() : centerPixel.y(); const double centerErrorAxisCoord = errorAxis->pixelToCoord( centerErrorAxisPixel); // depending on plottable, this might be different from just mDataPlottable->interface1D()->dataMainKey/Value const double symbolGap = mSymbolGap * 0.5 * errorAxis->pixelOrientation(); // plus error: double errorStart, errorEnd; if (!qIsNaN(it->errorPlus)) { errorStart = centerErrorAxisPixel + symbolGap; errorEnd = errorAxis->coordToPixel(centerErrorAxisCoord + it->errorPlus); if (errorAxis->orientation() == Qt::Vertical) { if ((errorStart > errorEnd) != errorAxis->rangeReversed()) backbones.append(QLineF(centerOrthoAxisPixel, errorStart, centerOrthoAxisPixel, errorEnd)); whiskers.append(QLineF(centerOrthoAxisPixel - mWhiskerWidth * 0.5, errorEnd, centerOrthoAxisPixel + mWhiskerWidth * 0.5, errorEnd)); } else { if ((errorStart < errorEnd) != errorAxis->rangeReversed()) backbones.append(QLineF(errorStart, centerOrthoAxisPixel, errorEnd, centerOrthoAxisPixel)); whiskers.append(QLineF(errorEnd, centerOrthoAxisPixel - mWhiskerWidth * 0.5, errorEnd, centerOrthoAxisPixel + mWhiskerWidth * 0.5)); } } // minus error: if (!qIsNaN(it->errorMinus)) { errorStart = centerErrorAxisPixel - symbolGap; errorEnd = errorAxis->coordToPixel(centerErrorAxisCoord - it->errorMinus); if (errorAxis->orientation() == Qt::Vertical) { if ((errorStart < errorEnd) != errorAxis->rangeReversed()) backbones.append(QLineF(centerOrthoAxisPixel, errorStart, centerOrthoAxisPixel, errorEnd)); whiskers.append(QLineF(centerOrthoAxisPixel - mWhiskerWidth * 0.5, errorEnd, centerOrthoAxisPixel + mWhiskerWidth * 0.5, errorEnd)); } else { if ((errorStart > errorEnd) != errorAxis->rangeReversed()) backbones.append(QLineF(errorStart, centerOrthoAxisPixel, errorEnd, centerOrthoAxisPixel)); whiskers.append(QLineF(errorEnd, centerOrthoAxisPixel - mWhiskerWidth * 0.5, errorEnd, centerOrthoAxisPixel + mWhiskerWidth * 0.5)); } } } /*! \internal This method outputs the currently visible data range via \a begin and \a end. The returned range will also never exceed \a rangeRestriction. Since error bars with type \ref etKeyError may extend to arbitrarily positive and negative key coordinates relative to their data point key, this method checks all outer error bars whether they truly don't reach into the visible portion of the axis rect, by calling \ref errorBarVisible. On the other hand error bars with type \ref etValueError that are associated with data plottables whose sort key is equal to the main key (see \ref qcpdatacontainer-datatype "QCPDataContainer DataType") can be handled very efficiently by finding the visible range of error bars through binary search (\ref QCPPlottableInterface1D::findBegin and \ref QCPPlottableInterface1D::findEnd). If the plottable's sort key is not equal to the main key, this method returns the full data range, only restricted by \a rangeRestriction. Drawing optimization then has to be done on a point-by-point basis in the \ref draw method. */ void QCPErrorBars::getVisibleDataBounds(QCPErrorBarsDataContainer::const_iterator &begin, QCPErrorBarsDataContainer::const_iterator &end, const QCPDataRange &rangeRestriction) const { QCPAxis *keyAxis = mKeyAxis.data(); QCPAxis *valueAxis = mValueAxis.data(); if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; end = mDataContainer->constEnd(); begin = end; return; } if (!mDataPlottable || rangeRestriction.isEmpty()) { end = mDataContainer->constEnd(); begin = end; return; } if (!mDataPlottable->interface1D()->sortKeyIsMainKey()) { // if the sort key isn't the main key, it's not possible to find a contiguous range of visible // data points, so this method then only applies the range restriction and otherwise returns // the full data range. Visibility checks must be done on a per-datapoin-basis during drawing QCPDataRange dataRange(0, mDataContainer->size()); dataRange = dataRange.bounded(rangeRestriction); begin = mDataContainer->constBegin() + dataRange.begin(); end = mDataContainer->constBegin() + dataRange.end(); return; } // get visible data range via interface from data plottable, and then restrict to available error data points: const int n = qMin(mDataContainer->size(), mDataPlottable->interface1D()->dataCount()); int beginIndex = mDataPlottable->interface1D()->findBegin(keyAxis->range().lower); int endIndex = mDataPlottable->interface1D()->findEnd(keyAxis->range().upper); int i = beginIndex; while (i > 0 && i < n && i > rangeRestriction.begin()) { if (errorBarVisible(i)) beginIndex = i; --i; } i = endIndex; while (i >= 0 && i < n && i < rangeRestriction.end()) { if (errorBarVisible(i)) endIndex = i + 1; ++i; } QCPDataRange dataRange(beginIndex, endIndex); dataRange = dataRange.bounded(rangeRestriction.bounded(QCPDataRange(0, mDataContainer->size()))); begin = mDataContainer->constBegin() + dataRange.begin(); end = mDataContainer->constBegin() + dataRange.end(); } /*! \internal Calculates the minimum distance in pixels the error bars' representation has from the given \a pixelPoint. This is used to determine whether the error bar was clicked or not, e.g. in \ref selectTest. The closest data point to \a pixelPoint is returned in \a closestData. */ double QCPErrorBars::pointDistance(const QPointF &pixelPoint, QCPErrorBarsDataContainer::const_iterator &closestData) const { closestData = mDataContainer->constEnd(); if (!mDataPlottable || mDataContainer->isEmpty()) return -1.0; QCPErrorBarsDataContainer::const_iterator begin, end; getVisibleDataBounds(begin, end, QCPDataRange(0, dataCount())); // calculate minimum distances to error backbones (whiskers are ignored for speed) and find closestData iterator: double minDistSqr = std::numeric_limits::max(); QVector backbones, whiskers; for (QCPErrorBarsDataContainer::const_iterator it = begin; it != end; ++it) { getErrorBarLines(it, backbones, whiskers); for (int i = 0; i < backbones.size(); ++i) { const double currentDistSqr = QCPVector2D(pixelPoint).distanceSquaredToLine(backbones.at(i)); if (currentDistSqr < minDistSqr) { minDistSqr = currentDistSqr; closestData = it; } } } return qSqrt(minDistSqr); } /*! \internal \note This method is identical to \ref QCPAbstractPlottable1D::getDataSegments but needs to be reproduced here since the \ref QCPErrorBars plottable, as a special case that doesn't have its own key/value data coordinates, doesn't derive from \ref QCPAbstractPlottable1D. See the documentation there for details. */ void QCPErrorBars::getDataSegments(QList &selectedSegments, QList &unselectedSegments) const { selectedSegments.clear(); unselectedSegments.clear(); if (mSelectable == QCP::stWhole) // stWhole selection type draws the entire plottable with selected style if mSelection isn't empty { if (selected()) selectedSegments << QCPDataRange(0, dataCount()); else unselectedSegments << QCPDataRange(0, dataCount()); } else { QCPDataSelection sel(selection()); sel.simplify(); selectedSegments = sel.dataRanges(); unselectedSegments = sel.inverse(QCPDataRange(0, dataCount())).dataRanges(); } } /*! \internal Returns whether the error bar at the specified \a index is visible within the current key axis range. This method assumes for performance reasons without checking that the key axis, the value axis, and the data plottable (\ref setDataPlottable) are not zero and that \a index is within valid bounds of this \ref QCPErrorBars instance and the bounds of the data plottable. */ bool QCPErrorBars::errorBarVisible(int index) const { QPointF centerPixel = mDataPlottable->interface1D()->dataPixelPosition(index); const double centerKeyPixel = mKeyAxis->orientation() == Qt::Horizontal ? centerPixel.x() : centerPixel.y(); if (qIsNaN(centerKeyPixel)) return false; double keyMin, keyMax; if (mErrorType == etKeyError) { const double centerKey = mKeyAxis->pixelToCoord(centerKeyPixel); const double errorPlus = mDataContainer->at(index).errorPlus; const double errorMinus = mDataContainer->at(index).errorMinus; keyMax = centerKey + (qIsNaN(errorPlus) ? 0 : errorPlus); keyMin = centerKey - (qIsNaN(errorMinus) ? 0 : errorMinus); } else // mErrorType == etValueError { keyMax = mKeyAxis->pixelToCoord(centerKeyPixel + mWhiskerWidth * 0.5 * mKeyAxis->pixelOrientation()); keyMin = mKeyAxis->pixelToCoord(centerKeyPixel - mWhiskerWidth * 0.5 * mKeyAxis->pixelOrientation()); } return ((keyMax > mKeyAxis->range().lower) && (keyMin < mKeyAxis->range().upper)); } /*! \internal Returns whether \a line intersects (or is contained in) \a pixelRect. \a line is assumed to be either perfectly horizontal or perfectly vertical, as is the case for error bar lines. */ bool QCPErrorBars::rectIntersectsLine(const QRectF &pixelRect, const QLineF &line) const { if (pixelRect.left() > line.x1() && pixelRect.left() > line.x2()) return false; else if (pixelRect.right() < line.x1() && pixelRect.right() < line.x2()) return false; else if (pixelRect.top() > line.y1() && pixelRect.top() > line.y2()) return false; else if (pixelRect.bottom() < line.y1() && pixelRect.bottom() < line.y2()) return false; else return true; } /* end of 'src/plottables/plottable-errorbar.cpp' */ /* including file 'src/items/item-straightline.cpp', size 7592 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPItemStraightLine //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPItemStraightLine \brief A straight line that spans infinitely in both directions \image html QCPItemStraightLine.png "Straight line example. Blue dotted circles are anchors, solid blue discs are positions." It has two positions, \a point1 and \a point2, which define the straight line. */ /*! Creates a straight line item and sets default values. The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead. */ QCPItemStraightLine::QCPItemStraightLine(QCustomPlot *parentPlot) : QCPAbstractItem(parentPlot), point1(createPosition(QLatin1String("point1"))), point2(createPosition(QLatin1String("point2"))) { point1->setCoords(0, 0); point2->setCoords(1, 1); setPen(QPen(Qt::black)); setSelectedPen(QPen(Qt::blue, 2)); } QCPItemStraightLine::~QCPItemStraightLine() { } /*! Sets the pen that will be used to draw the line \see setSelectedPen */ void QCPItemStraightLine::setPen(const QPen &pen) { mPen = pen; } /*! Sets the pen that will be used to draw the line when selected \see setPen, setSelected */ void QCPItemStraightLine::setSelectedPen(const QPen &pen) { mSelectedPen = pen; } /* inherits documentation from base class */ double QCPItemStraightLine::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if (onlySelectable && !mSelectable) return -1; return QCPVector2D(pos).distanceToStraightLine(point1->pixelPosition(), point2->pixelPosition() - point1->pixelPosition()); } /* inherits documentation from base class */ void QCPItemStraightLine::draw(QCPPainter *painter) { QCPVector2D start(point1->pixelPosition()); QCPVector2D end(point2->pixelPosition()); // get visible segment of straight line inside clipRect: double clipPad = mainPen().widthF(); QLineF line = getRectClippedStraightLine(start, end - start, clipRect().adjusted(-clipPad, -clipPad, clipPad, clipPad)); // paint visible segment, if existent: if (!line.isNull()) { painter->setPen(mainPen()); painter->drawLine(line); } } /*! \internal Returns the section of the straight line defined by \a base and direction vector \a vec, that is visible in the specified \a rect. This is a helper function for \ref draw. */ QLineF QCPItemStraightLine::getRectClippedStraightLine(const QCPVector2D &base, const QCPVector2D &vec, const QRect &rect) const { double bx, by; double gamma; QLineF result; if (vec.x() == 0 && vec.y() == 0) return result; if (qFuzzyIsNull(vec.x())) // line is vertical { // check top of rect: bx = rect.left(); by = rect.top(); gamma = base.x() - bx + (by - base.y()) * vec.x() / vec.y(); if (gamma >= 0 && gamma <= rect.width()) result.setLine(bx + gamma, rect.top(), bx + gamma, rect.bottom()); // no need to check bottom because we know line is vertical } else if (qFuzzyIsNull(vec.y())) // line is horizontal { // check left of rect: bx = rect.left(); by = rect.top(); gamma = base.y() - by + (bx - base.x()) * vec.y() / vec.x(); if (gamma >= 0 && gamma <= rect.height()) result.setLine(rect.left(), by + gamma, rect.right(), by + gamma); // no need to check right because we know line is horizontal } else // line is skewed { QList pointVectors; // check top of rect: bx = rect.left(); by = rect.top(); gamma = base.x() - bx + (by - base.y()) * vec.x() / vec.y(); if (gamma >= 0 && gamma <= rect.width()) pointVectors.append(QCPVector2D(bx + gamma, by)); // check bottom of rect: bx = rect.left(); by = rect.bottom(); gamma = base.x() - bx + (by - base.y()) * vec.x() / vec.y(); if (gamma >= 0 && gamma <= rect.width()) pointVectors.append(QCPVector2D(bx + gamma, by)); // check left of rect: bx = rect.left(); by = rect.top(); gamma = base.y() - by + (bx - base.x()) * vec.y() / vec.x(); if (gamma >= 0 && gamma <= rect.height()) pointVectors.append(QCPVector2D(bx, by + gamma)); // check right of rect: bx = rect.right(); by = rect.top(); gamma = base.y() - by + (bx - base.x()) * vec.y() / vec.x(); if (gamma >= 0 && gamma <= rect.height()) pointVectors.append(QCPVector2D(bx, by + gamma)); // evaluate points: if (pointVectors.size() == 2) { result.setPoints(pointVectors.at(0).toPointF(), pointVectors.at(1).toPointF()); } else if (pointVectors.size() > 2) { // line probably goes through corner of rect, and we got two points there. single out the point pair with greatest distance: double distSqrMax = 0; QCPVector2D pv1, pv2; for (int i = 0; i < pointVectors.size() - 1; ++i) { for (int k = i + 1; k < pointVectors.size(); ++k) { double distSqr = (pointVectors.at(i) - pointVectors.at(k)).lengthSquared(); if (distSqr > distSqrMax) { pv1 = pointVectors.at(i); pv2 = pointVectors.at(k); distSqrMax = distSqr; } } } result.setPoints(pv1.toPointF(), pv2.toPointF()); } } return result; } /*! \internal Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected and mSelectedPen when it is. */ QPen QCPItemStraightLine::mainPen() const { return mSelected ? mSelectedPen : mPen; } /* end of 'src/items/item-straightline.cpp' */ /* including file 'src/items/item-line.cpp', size 8498 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPItemLine //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPItemLine \brief A line from one point to another \image html QCPItemLine.png "Line example. Blue dotted circles are anchors, solid blue discs are positions." It has two positions, \a start and \a end, which define the end points of the line. With \ref setHead and \ref setTail you may set different line ending styles, e.g. to create an arrow. */ /*! Creates a line item and sets default values. The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead. */ QCPItemLine::QCPItemLine(QCustomPlot *parentPlot) : QCPAbstractItem(parentPlot), start(createPosition(QLatin1String("start"))), end(createPosition(QLatin1String("end"))) { start->setCoords(0, 0); end->setCoords(1, 1); setPen(QPen(Qt::black)); setSelectedPen(QPen(Qt::blue, 2)); } QCPItemLine::~QCPItemLine() { } /*! Sets the pen that will be used to draw the line \see setSelectedPen */ void QCPItemLine::setPen(const QPen &pen) { mPen = pen; } /*! Sets the pen that will be used to draw the line when selected \see setPen, setSelected */ void QCPItemLine::setSelectedPen(const QPen &pen) { mSelectedPen = pen; } /*! Sets the line ending style of the head. The head corresponds to the \a end position. Note that due to the overloaded QCPLineEnding constructor, you may directly specify a QCPLineEnding::EndingStyle here, e.g. \code setHead(QCPLineEnding::esSpikeArrow) \endcode \see setTail */ void QCPItemLine::setHead(const QCPLineEnding &head) { mHead = head; } /*! Sets the line ending style of the tail. The tail corresponds to the \a start position. Note that due to the overloaded QCPLineEnding constructor, you may directly specify a QCPLineEnding::EndingStyle here, e.g. \code setTail(QCPLineEnding::esSpikeArrow) \endcode \see setHead */ void QCPItemLine::setTail(const QCPLineEnding &tail) { mTail = tail; } /* inherits documentation from base class */ double QCPItemLine::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if (onlySelectable && !mSelectable) return -1; return qSqrt(QCPVector2D(pos).distanceSquaredToLine(start->pixelPosition(), end->pixelPosition())); } /* inherits documentation from base class */ void QCPItemLine::draw(QCPPainter *painter) { QCPVector2D startVec(start->pixelPosition()); QCPVector2D endVec(end->pixelPosition()); if (qFuzzyIsNull((startVec - endVec).lengthSquared())) return; // get visible segment of straight line inside clipRect: double clipPad = qMax(mHead.boundingDistance(), mTail.boundingDistance()); clipPad = qMax(clipPad, (double)mainPen().widthF()); QLineF line = getRectClippedLine(startVec, endVec, clipRect().adjusted(-clipPad, -clipPad, clipPad, clipPad)); // paint visible segment, if existent: if (!line.isNull()) { painter->setPen(mainPen()); painter->drawLine(line); painter->setBrush(Qt::SolidPattern); if (mTail.style() != QCPLineEnding::esNone) mTail.draw(painter, startVec, startVec - endVec); if (mHead.style() != QCPLineEnding::esNone) mHead.draw(painter, endVec, endVec - startVec); } } /*! \internal Returns the section of the line defined by \a start and \a end, that is visible in the specified \a rect. This is a helper function for \ref draw. */ QLineF QCPItemLine::getRectClippedLine(const QCPVector2D &start, const QCPVector2D &end, const QRect &rect) const { bool containsStart = rect.contains(start.x(), start.y()); bool containsEnd = rect.contains(end.x(), end.y()); if (containsStart && containsEnd) return QLineF(start.toPointF(), end.toPointF()); QCPVector2D base = start; QCPVector2D vec = end - start; double bx, by; double gamma, mu; QLineF result; QList pointVectors; if (!qFuzzyIsNull(vec.y())) // line is not horizontal { // check top of rect: bx = rect.left(); by = rect.top(); mu = (by - base.y()) / vec.y(); if (mu >= 0 && mu <= 1) { gamma = base.x() - bx + mu * vec.x(); if (gamma >= 0 && gamma <= rect.width()) pointVectors.append(QCPVector2D(bx + gamma, by)); } // check bottom of rect: bx = rect.left(); by = rect.bottom(); mu = (by - base.y()) / vec.y(); if (mu >= 0 && mu <= 1) { gamma = base.x() - bx + mu * vec.x(); if (gamma >= 0 && gamma <= rect.width()) pointVectors.append(QCPVector2D(bx + gamma, by)); } } if (!qFuzzyIsNull(vec.x())) // line is not vertical { // check left of rect: bx = rect.left(); by = rect.top(); mu = (bx - base.x()) / vec.x(); if (mu >= 0 && mu <= 1) { gamma = base.y() - by + mu * vec.y(); if (gamma >= 0 && gamma <= rect.height()) pointVectors.append(QCPVector2D(bx, by + gamma)); } // check right of rect: bx = rect.right(); by = rect.top(); mu = (bx - base.x()) / vec.x(); if (mu >= 0 && mu <= 1) { gamma = base.y() - by + mu * vec.y(); if (gamma >= 0 && gamma <= rect.height()) pointVectors.append(QCPVector2D(bx, by + gamma)); } } if (containsStart) pointVectors.append(start); if (containsEnd) pointVectors.append(end); // evaluate points: if (pointVectors.size() == 2) { result.setPoints(pointVectors.at(0).toPointF(), pointVectors.at(1).toPointF()); } else if (pointVectors.size() > 2) { // line probably goes through corner of rect, and we got two points there. single out the point pair with greatest distance: double distSqrMax = 0; QCPVector2D pv1, pv2; for (int i = 0; i < pointVectors.size() - 1; ++i) { for (int k = i + 1; k < pointVectors.size(); ++k) { double distSqr = (pointVectors.at(i) - pointVectors.at(k)).lengthSquared(); if (distSqr > distSqrMax) { pv1 = pointVectors.at(i); pv2 = pointVectors.at(k); distSqrMax = distSqr; } } } result.setPoints(pv1.toPointF(), pv2.toPointF()); } return result; } /*! \internal Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected and mSelectedPen when it is. */ QPen QCPItemLine::mainPen() const { return mSelected ? mSelectedPen : mPen; } /* end of 'src/items/item-line.cpp' */ /* including file 'src/items/item-curve.cpp', size 7159 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPItemCurve //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPItemCurve \brief A curved line from one point to another \image html QCPItemCurve.png "Curve example. Blue dotted circles are anchors, solid blue discs are positions." It has four positions, \a start and \a end, which define the end points of the line, and two control points which define the direction the line exits from the start and the direction from which it approaches the end: \a startDir and \a endDir. With \ref setHead and \ref setTail you may set different line ending styles, e.g. to create an arrow. Often it is desirable for the control points to stay at fixed relative positions to the start/end point. This can be achieved by setting the parent anchor e.g. of \a startDir simply to \a start, and then specify the desired pixel offset with QCPItemPosition::setCoords on \a startDir. */ /*! Creates a curve item and sets default values. The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead. */ QCPItemCurve::QCPItemCurve(QCustomPlot *parentPlot) : QCPAbstractItem(parentPlot), start(createPosition(QLatin1String("start"))), startDir(createPosition(QLatin1String("startDir"))), endDir(createPosition(QLatin1String("endDir"))), end(createPosition(QLatin1String("end"))) { start->setCoords(0, 0); startDir->setCoords(0.5, 0); endDir->setCoords(0, 0.5); end->setCoords(1, 1); setPen(QPen(Qt::black)); setSelectedPen(QPen(Qt::blue, 2)); } QCPItemCurve::~QCPItemCurve() { } /*! Sets the pen that will be used to draw the line \see setSelectedPen */ void QCPItemCurve::setPen(const QPen &pen) { mPen = pen; } /*! Sets the pen that will be used to draw the line when selected \see setPen, setSelected */ void QCPItemCurve::setSelectedPen(const QPen &pen) { mSelectedPen = pen; } /*! Sets the line ending style of the head. The head corresponds to the \a end position. Note that due to the overloaded QCPLineEnding constructor, you may directly specify a QCPLineEnding::EndingStyle here, e.g. \code setHead(QCPLineEnding::esSpikeArrow) \endcode \see setTail */ void QCPItemCurve::setHead(const QCPLineEnding &head) { mHead = head; } /*! Sets the line ending style of the tail. The tail corresponds to the \a start position. Note that due to the overloaded QCPLineEnding constructor, you may directly specify a QCPLineEnding::EndingStyle here, e.g. \code setTail(QCPLineEnding::esSpikeArrow) \endcode \see setHead */ void QCPItemCurve::setTail(const QCPLineEnding &tail) { mTail = tail; } /* inherits documentation from base class */ double QCPItemCurve::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if (onlySelectable && !mSelectable) return -1; QPointF startVec(start->pixelPosition()); QPointF startDirVec(startDir->pixelPosition()); QPointF endDirVec(endDir->pixelPosition()); QPointF endVec(end->pixelPosition()); QPainterPath cubicPath(startVec); cubicPath.cubicTo(startDirVec, endDirVec, endVec); QPolygonF polygon = cubicPath.toSubpathPolygons().first(); QCPVector2D p(pos); double minDistSqr = std::numeric_limits::max(); for (int i = 1; i < polygon.size(); ++i) { double distSqr = p.distanceSquaredToLine(polygon.at(i - 1), polygon.at(i)); if (distSqr < minDistSqr) minDistSqr = distSqr; } return qSqrt(minDistSqr); } /* inherits documentation from base class */ void QCPItemCurve::draw(QCPPainter *painter) { QCPVector2D startVec(start->pixelPosition()); QCPVector2D startDirVec(startDir->pixelPosition()); QCPVector2D endDirVec(endDir->pixelPosition()); QCPVector2D endVec(end->pixelPosition()); if ((endVec - startVec).length() > 1e10) // too large curves cause crash return; QPainterPath cubicPath(startVec.toPointF()); cubicPath.cubicTo(startDirVec.toPointF(), endDirVec.toPointF(), endVec.toPointF()); // paint visible segment, if existent: QRect clip = clipRect().adjusted(-mainPen().widthF(), -mainPen().widthF(), mainPen().widthF(), mainPen().widthF()); QRect cubicRect = cubicPath.controlPointRect().toRect(); if (cubicRect.isEmpty()) // may happen when start and end exactly on same x or y position cubicRect.adjust(0, 0, 1, 1); if (clip.intersects(cubicRect)) { painter->setPen(mainPen()); painter->drawPath(cubicPath); painter->setBrush(Qt::SolidPattern); if (mTail.style() != QCPLineEnding::esNone) mTail.draw(painter, startVec, M_PI - cubicPath.angleAtPercent(0) / 180.0 * M_PI); if (mHead.style() != QCPLineEnding::esNone) mHead.draw(painter, endVec, -cubicPath.angleAtPercent(1) / 180.0 * M_PI); } } /*! \internal Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected and mSelectedPen when it is. */ QPen QCPItemCurve::mainPen() const { return mSelected ? mSelectedPen : mPen; } /* end of 'src/items/item-curve.cpp' */ /* including file 'src/items/item-rect.cpp', size 6479 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPItemRect //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPItemRect \brief A rectangle \image html QCPItemRect.png "Rectangle example. Blue dotted circles are anchors, solid blue discs are positions." It has two positions, \a topLeft and \a bottomRight, which define the rectangle. */ /*! Creates a rectangle item and sets default values. The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead. */ QCPItemRect::QCPItemRect(QCustomPlot *parentPlot) : QCPAbstractItem(parentPlot), topLeft(createPosition(QLatin1String("topLeft"))), bottomRight(createPosition(QLatin1String("bottomRight"))), top(createAnchor(QLatin1String("top"), aiTop)), topRight(createAnchor(QLatin1String("topRight"), aiTopRight)), right(createAnchor(QLatin1String("right"), aiRight)), bottom(createAnchor(QLatin1String("bottom"), aiBottom)), bottomLeft(createAnchor(QLatin1String("bottomLeft"), aiBottomLeft)), left(createAnchor(QLatin1String("left"), aiLeft)) { topLeft->setCoords(0, 1); bottomRight->setCoords(1, 0); setPen(QPen(Qt::black)); setSelectedPen(QPen(Qt::blue, 2)); setBrush(Qt::NoBrush); setSelectedBrush(Qt::NoBrush); } QCPItemRect::~QCPItemRect() { } /*! Sets the pen that will be used to draw the line of the rectangle \see setSelectedPen, setBrush */ void QCPItemRect::setPen(const QPen &pen) { mPen = pen; } /*! Sets the pen that will be used to draw the line of the rectangle when selected \see setPen, setSelected */ void QCPItemRect::setSelectedPen(const QPen &pen) { mSelectedPen = pen; } /*! Sets the brush that will be used to fill the rectangle. To disable filling, set \a brush to Qt::NoBrush. \see setSelectedBrush, setPen */ void QCPItemRect::setBrush(const QBrush &brush) { mBrush = brush; } /*! Sets the brush that will be used to fill the rectangle when selected. To disable filling, set \a brush to Qt::NoBrush. \see setBrush */ void QCPItemRect::setSelectedBrush(const QBrush &brush) { mSelectedBrush = brush; } /* inherits documentation from base class */ double QCPItemRect::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if (onlySelectable && !mSelectable) return -1; QRectF rect = QRectF(topLeft->pixelPosition(), bottomRight->pixelPosition()).normalized(); bool filledRect = mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0; return rectDistance(rect, pos, filledRect); } /* inherits documentation from base class */ void QCPItemRect::draw(QCPPainter *painter) { QPointF p1 = topLeft->pixelPosition(); QPointF p2 = bottomRight->pixelPosition(); if (p1.toPoint() == p2.toPoint()) return; QRectF rect = QRectF(p1, p2).normalized(); double clipPad = mainPen().widthF(); QRectF boundingRect = rect.adjusted(-clipPad, -clipPad, clipPad, clipPad); if (boundingRect.intersects(clipRect())) // only draw if bounding rect of rect item is visible in cliprect { painter->setPen(mainPen()); painter->setBrush(mainBrush()); painter->drawRect(rect); } } /* inherits documentation from base class */ QPointF QCPItemRect::anchorPixelPosition(int anchorId) const { QRectF rect = QRectF(topLeft->pixelPosition(), bottomRight->pixelPosition()); switch (anchorId) { case aiTop: return (rect.topLeft() + rect.topRight()) * 0.5; case aiTopRight: return rect.topRight(); case aiRight: return (rect.topRight() + rect.bottomRight()) * 0.5; case aiBottom: return (rect.bottomLeft() + rect.bottomRight()) * 0.5; case aiBottomLeft: return rect.bottomLeft(); case aiLeft: return (rect.topLeft() + rect.bottomLeft()) * 0.5; } qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId; return QPointF(); } /*! \internal Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected and mSelectedPen when it is. */ QPen QCPItemRect::mainPen() const { return mSelected ? mSelectedPen : mPen; } /*! \internal Returns the brush that should be used for drawing fills of the item. Returns mBrush when the item is not selected and mSelectedBrush when it is. */ QBrush QCPItemRect::mainBrush() const { return mSelected ? mSelectedBrush : mBrush; } /* end of 'src/items/item-rect.cpp' */ /* including file 'src/items/item-text.cpp', size 13338 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPItemText //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPItemText \brief A text label \image html QCPItemText.png "Text example. Blue dotted circles are anchors, solid blue discs are positions." Its position is defined by the member \a position and the setting of \ref setPositionAlignment. The latter controls which part of the text rect shall be aligned with \a position. The text alignment itself (i.e. left, center, right) can be controlled with \ref setTextAlignment. The text may be rotated around the \a position point with \ref setRotation. */ /*! Creates a text item and sets default values. The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead. */ QCPItemText::QCPItemText(QCustomPlot *parentPlot) : QCPAbstractItem(parentPlot), position(createPosition(QLatin1String("position"))), topLeft(createAnchor(QLatin1String("topLeft"), aiTopLeft)), top(createAnchor(QLatin1String("top"), aiTop)), topRight(createAnchor(QLatin1String("topRight"), aiTopRight)), right(createAnchor(QLatin1String("right"), aiRight)), bottomRight(createAnchor(QLatin1String("bottomRight"), aiBottomRight)), bottom(createAnchor(QLatin1String("bottom"), aiBottom)), bottomLeft(createAnchor(QLatin1String("bottomLeft"), aiBottomLeft)), left(createAnchor(QLatin1String("left"), aiLeft)), mText(QLatin1String("text")), mPositionAlignment(Qt::AlignCenter), mTextAlignment(Qt::AlignTop | Qt::AlignHCenter), mRotation(0) { position->setCoords(0, 0); setPen(Qt::NoPen); setSelectedPen(Qt::NoPen); setBrush(Qt::NoBrush); setSelectedBrush(Qt::NoBrush); setColor(Qt::black); setSelectedColor(Qt::blue); } QCPItemText::~QCPItemText() { } /*! Sets the color of the text. */ void QCPItemText::setColor(const QColor &color) { mColor = color; } /*! Sets the color of the text that will be used when the item is selected. */ void QCPItemText::setSelectedColor(const QColor &color) { mSelectedColor = color; } /*! Sets the pen that will be used do draw a rectangular border around the text. To disable the border, set \a pen to Qt::NoPen. \see setSelectedPen, setBrush, setPadding */ void QCPItemText::setPen(const QPen &pen) { mPen = pen; } /*! Sets the pen that will be used do draw a rectangular border around the text, when the item is selected. To disable the border, set \a pen to Qt::NoPen. \see setPen */ void QCPItemText::setSelectedPen(const QPen &pen) { mSelectedPen = pen; } /*! Sets the brush that will be used do fill the background of the text. To disable the background, set \a brush to Qt::NoBrush. \see setSelectedBrush, setPen, setPadding */ void QCPItemText::setBrush(const QBrush &brush) { mBrush = brush; } /*! Sets the brush that will be used do fill the background of the text, when the item is selected. To disable the background, set \a brush to Qt::NoBrush. \see setBrush */ void QCPItemText::setSelectedBrush(const QBrush &brush) { mSelectedBrush = brush; } /*! Sets the font of the text. \see setSelectedFont, setColor */ void QCPItemText::setFont(const QFont &font) { mFont = font; } /*! Sets the font of the text that will be used when the item is selected. \see setFont */ void QCPItemText::setSelectedFont(const QFont &font) { mSelectedFont = font; } /*! Sets the text that will be displayed. Multi-line texts are supported by inserting a line break character, e.g. '\n'. \see setFont, setColor, setTextAlignment */ void QCPItemText::setText(const QString &text) { mText = text; } /*! Sets which point of the text rect shall be aligned with \a position. Examples: \li If \a alignment is Qt::AlignHCenter | Qt::AlignTop, the text will be positioned such that the top of the text rect will be horizontally centered on \a position. \li If \a alignment is Qt::AlignLeft | Qt::AlignBottom, \a position will indicate the bottom left corner of the text rect. If you want to control the alignment of (multi-lined) text within the text rect, use \ref setTextAlignment. */ void QCPItemText::setPositionAlignment(Qt::Alignment alignment) { mPositionAlignment = alignment; } /*! Controls how (multi-lined) text is aligned inside the text rect (typically Qt::AlignLeft, Qt::AlignCenter or Qt::AlignRight). */ void QCPItemText::setTextAlignment(Qt::Alignment alignment) { mTextAlignment = alignment; } /*! Sets the angle in degrees by which the text (and the text rectangle, if visible) will be rotated around \a position. */ void QCPItemText::setRotation(double degrees) { mRotation = degrees; } /*! Sets the distance between the border of the text rectangle and the text. The appearance (and visibility) of the text rectangle can be controlled with \ref setPen and \ref setBrush. */ void QCPItemText::setPadding(const QMargins &padding) { mPadding = padding; } /* inherits documentation from base class */ double QCPItemText::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if (onlySelectable && !mSelectable) return -1; // The rect may be rotated, so we transform the actual clicked pos to the rotated // coordinate system, so we can use the normal rectDistance function for non-rotated rects: QPointF positionPixels(position->pixelPosition()); QTransform inputTransform; inputTransform.translate(positionPixels.x(), positionPixels.y()); inputTransform.rotate(-mRotation); inputTransform.translate(-positionPixels.x(), -positionPixels.y()); QPointF rotatedPos = inputTransform.map(pos); QFontMetrics fontMetrics(mFont); QRect textRect = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip | mTextAlignment, mText); QRect textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(), mPadding.right(), mPadding.bottom()); QPointF textPos = getTextDrawPoint(positionPixels, textBoxRect, mPositionAlignment); textBoxRect.moveTopLeft(textPos.toPoint()); return rectDistance(textBoxRect, rotatedPos, true); } /* inherits documentation from base class */ void QCPItemText::draw(QCPPainter *painter) { QPointF pos(position->pixelPosition()); QTransform transform = painter->transform(); transform.translate(pos.x(), pos.y()); if (!qFuzzyIsNull(mRotation)) transform.rotate(mRotation); painter->setFont(mainFont()); QRect textRect = painter->fontMetrics().boundingRect(0, 0, 0, 0, Qt::TextDontClip | mTextAlignment, mText); QRect textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(), mPadding.right(), mPadding.bottom()); QPointF textPos = getTextDrawPoint(QPointF(0, 0), textBoxRect, mPositionAlignment); // 0, 0 because the transform does the translation textRect.moveTopLeft(textPos.toPoint() + QPoint(mPadding.left(), mPadding.top())); textBoxRect.moveTopLeft(textPos.toPoint()); double clipPad = mainPen().widthF(); QRect boundingRect = textBoxRect.adjusted(-clipPad, -clipPad, clipPad, clipPad); if (transform.mapRect(boundingRect).intersects(painter->transform().mapRect(clipRect()))) { painter->setTransform(transform); if ((mainBrush().style() != Qt::NoBrush && mainBrush().color().alpha() != 0) || (mainPen().style() != Qt::NoPen && mainPen().color().alpha() != 0)) { painter->setPen(mainPen()); painter->setBrush(mainBrush()); painter->drawRect(textBoxRect); } painter->setBrush(Qt::NoBrush); painter->setPen(QPen(mainColor())); painter->drawText(textRect, Qt::TextDontClip | mTextAlignment, mText); } } /* inherits documentation from base class */ QPointF QCPItemText::anchorPixelPosition(int anchorId) const { // get actual rect points (pretty much copied from draw function): QPointF pos(position->pixelPosition()); QTransform transform; transform.translate(pos.x(), pos.y()); if (!qFuzzyIsNull(mRotation)) transform.rotate(mRotation); QFontMetrics fontMetrics(mainFont()); QRect textRect = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip | mTextAlignment, mText); QRectF textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(), mPadding.right(), mPadding.bottom()); QPointF textPos = getTextDrawPoint(QPointF(0, 0), textBoxRect, mPositionAlignment); // 0, 0 because the transform does the translation textBoxRect.moveTopLeft(textPos.toPoint()); QPolygonF rectPoly = transform.map(QPolygonF(textBoxRect)); switch (anchorId) { case aiTopLeft: return rectPoly.at(0); case aiTop: return (rectPoly.at(0) + rectPoly.at(1)) * 0.5; case aiTopRight: return rectPoly.at(1); case aiRight: return (rectPoly.at(1) + rectPoly.at(2)) * 0.5; case aiBottomRight: return rectPoly.at(2); case aiBottom: return (rectPoly.at(2) + rectPoly.at(3)) * 0.5; case aiBottomLeft: return rectPoly.at(3); case aiLeft: return (rectPoly.at(3) + rectPoly.at(0)) * 0.5; } qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId; return QPointF(); } /*! \internal Returns the point that must be given to the QPainter::drawText function (which expects the top left point of the text rect), according to the position \a pos, the text bounding box \a rect and the requested \a positionAlignment. For example, if \a positionAlignment is Qt::AlignLeft | Qt::AlignBottom the returned point will be shifted upward by the height of \a rect, starting from \a pos. So if the text is finally drawn at that point, the lower left corner of the resulting text rect is at \a pos. */ QPointF QCPItemText::getTextDrawPoint(const QPointF &pos, const QRectF &rect, Qt::Alignment positionAlignment) const { if (positionAlignment == 0 || positionAlignment == (Qt::AlignLeft | Qt::AlignTop)) return pos; QPointF result = pos; // start at top left if (positionAlignment.testFlag(Qt::AlignHCenter)) result.rx() -= rect.width() / 2.0; else if (positionAlignment.testFlag(Qt::AlignRight)) result.rx() -= rect.width(); if (positionAlignment.testFlag(Qt::AlignVCenter)) result.ry() -= rect.height() / 2.0; else if (positionAlignment.testFlag(Qt::AlignBottom)) result.ry() -= rect.height(); return result; } /*! \internal Returns the font that should be used for drawing text. Returns mFont when the item is not selected and mSelectedFont when it is. */ QFont QCPItemText::mainFont() const { return mSelected ? mSelectedFont : mFont; } /*! \internal Returns the color that should be used for drawing text. Returns mColor when the item is not selected and mSelectedColor when it is. */ QColor QCPItemText::mainColor() const { return mSelected ? mSelectedColor : mColor; } /*! \internal Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected and mSelectedPen when it is. */ QPen QCPItemText::mainPen() const { return mSelected ? mSelectedPen : mPen; } /*! \internal Returns the brush that should be used for drawing fills of the item. Returns mBrush when the item is not selected and mSelectedBrush when it is. */ QBrush QCPItemText::mainBrush() const { return mSelected ? mSelectedBrush : mBrush; } /* end of 'src/items/item-text.cpp' */ /* including file 'src/items/item-ellipse.cpp', size 7863 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPItemEllipse //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPItemEllipse \brief An ellipse \image html QCPItemEllipse.png "Ellipse example. Blue dotted circles are anchors, solid blue discs are positions." It has two positions, \a topLeft and \a bottomRight, which define the rect the ellipse will be drawn in. */ /*! Creates an ellipse item and sets default values. The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead. */ QCPItemEllipse::QCPItemEllipse(QCustomPlot *parentPlot) : QCPAbstractItem(parentPlot), topLeft(createPosition(QLatin1String("topLeft"))), bottomRight(createPosition(QLatin1String("bottomRight"))), topLeftRim(createAnchor(QLatin1String("topLeftRim"), aiTopLeftRim)), top(createAnchor(QLatin1String("top"), aiTop)), topRightRim(createAnchor(QLatin1String("topRightRim"), aiTopRightRim)), right(createAnchor(QLatin1String("right"), aiRight)), bottomRightRim(createAnchor(QLatin1String("bottomRightRim"), aiBottomRightRim)), bottom(createAnchor(QLatin1String("bottom"), aiBottom)), bottomLeftRim(createAnchor(QLatin1String("bottomLeftRim"), aiBottomLeftRim)), left(createAnchor(QLatin1String("left"), aiLeft)), center(createAnchor(QLatin1String("center"), aiCenter)) { topLeft->setCoords(0, 1); bottomRight->setCoords(1, 0); setPen(QPen(Qt::black)); setSelectedPen(QPen(Qt::blue, 2)); setBrush(Qt::NoBrush); setSelectedBrush(Qt::NoBrush); } QCPItemEllipse::~QCPItemEllipse() { } /*! Sets the pen that will be used to draw the line of the ellipse \see setSelectedPen, setBrush */ void QCPItemEllipse::setPen(const QPen &pen) { mPen = pen; } /*! Sets the pen that will be used to draw the line of the ellipse when selected \see setPen, setSelected */ void QCPItemEllipse::setSelectedPen(const QPen &pen) { mSelectedPen = pen; } /*! Sets the brush that will be used to fill the ellipse. To disable filling, set \a brush to Qt::NoBrush. \see setSelectedBrush, setPen */ void QCPItemEllipse::setBrush(const QBrush &brush) { mBrush = brush; } /*! Sets the brush that will be used to fill the ellipse when selected. To disable filling, set \a brush to Qt::NoBrush. \see setBrush */ void QCPItemEllipse::setSelectedBrush(const QBrush &brush) { mSelectedBrush = brush; } /* inherits documentation from base class */ double QCPItemEllipse::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if (onlySelectable && !mSelectable) return -1; QPointF p1 = topLeft->pixelPosition(); QPointF p2 = bottomRight->pixelPosition(); QPointF center((p1 + p2) / 2.0); double a = qAbs(p1.x() - p2.x()) / 2.0; double b = qAbs(p1.y() - p2.y()) / 2.0; double x = pos.x() - center.x(); double y = pos.y() - center.y(); // distance to border: double c = 1.0 / qSqrt(x * x / (a * a) + y * y / (b * b)); double result = qAbs(c - 1) * qSqrt(x * x + y * y); // filled ellipse, allow click inside to count as hit: if (result > mParentPlot->selectionTolerance() * 0.99 && mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0) { if (x * x / (a * a) + y * y / (b * b) <= 1) result = mParentPlot->selectionTolerance() * 0.99; } return result; } /* inherits documentation from base class */ void QCPItemEllipse::draw(QCPPainter *painter) { QPointF p1 = topLeft->pixelPosition(); QPointF p2 = bottomRight->pixelPosition(); if (p1.toPoint() == p2.toPoint()) return; QRectF ellipseRect = QRectF(p1, p2).normalized(); QRect clip = clipRect().adjusted(-mainPen().widthF(), -mainPen().widthF(), mainPen().widthF(), mainPen().widthF()); if (ellipseRect.intersects(clip)) // only draw if bounding rect of ellipse is visible in cliprect { painter->setPen(mainPen()); painter->setBrush(mainBrush()); #ifdef __EXCEPTIONS try // drawEllipse sometimes throws exceptions if ellipse is too big { #endif painter->drawEllipse(ellipseRect); #ifdef __EXCEPTIONS } catch (...) { qDebug() << Q_FUNC_INFO << "Item too large for memory, setting invisible"; setVisible(false); } #endif } } /* inherits documentation from base class */ QPointF QCPItemEllipse::anchorPixelPosition(int anchorId) const { QRectF rect = QRectF(topLeft->pixelPosition(), bottomRight->pixelPosition()); switch (anchorId) { case aiTopLeftRim: return rect.center() + (rect.topLeft() - rect.center()) * 1 / qSqrt(2); case aiTop: return (rect.topLeft() + rect.topRight()) * 0.5; case aiTopRightRim: return rect.center() + (rect.topRight() - rect.center()) * 1 / qSqrt(2); case aiRight: return (rect.topRight() + rect.bottomRight()) * 0.5; case aiBottomRightRim: return rect.center() + (rect.bottomRight() - rect.center()) * 1 / qSqrt(2); case aiBottom: return (rect.bottomLeft() + rect.bottomRight()) * 0.5; case aiBottomLeftRim: return rect.center() + (rect.bottomLeft() - rect.center()) * 1 / qSqrt(2); case aiLeft: return (rect.topLeft() + rect.bottomLeft()) * 0.5; case aiCenter: return (rect.topLeft() + rect.bottomRight()) * 0.5; } qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId; return QPointF(); } /*! \internal Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected and mSelectedPen when it is. */ QPen QCPItemEllipse::mainPen() const { return mSelected ? mSelectedPen : mPen; } /*! \internal Returns the brush that should be used for drawing fills of the item. Returns mBrush when the item is not selected and mSelectedBrush when it is. */ QBrush QCPItemEllipse::mainBrush() const { return mSelected ? mSelectedBrush : mBrush; } /* end of 'src/items/item-ellipse.cpp' */ /* including file 'src/items/item-pixmap.cpp', size 10615 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPItemPixmap //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPItemPixmap \brief An arbitrary pixmap \image html QCPItemPixmap.png "Pixmap example. Blue dotted circles are anchors, solid blue discs are positions." It has two positions, \a topLeft and \a bottomRight, which define the rectangle the pixmap will be drawn in. Depending on the scale setting (\ref setScaled), the pixmap will be either scaled to fit the rectangle or be drawn aligned to the topLeft position. If scaling is enabled and \a topLeft is further to the bottom/right than \a bottomRight (as shown on the right side of the example image), the pixmap will be flipped in the respective orientations. */ /*! Creates a rectangle item and sets default values. The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead. */ QCPItemPixmap::QCPItemPixmap(QCustomPlot *parentPlot) : QCPAbstractItem(parentPlot), topLeft(createPosition(QLatin1String("topLeft"))), bottomRight(createPosition(QLatin1String("bottomRight"))), top(createAnchor(QLatin1String("top"), aiTop)), topRight(createAnchor(QLatin1String("topRight"), aiTopRight)), right(createAnchor(QLatin1String("right"), aiRight)), bottom(createAnchor(QLatin1String("bottom"), aiBottom)), bottomLeft(createAnchor(QLatin1String("bottomLeft"), aiBottomLeft)), left(createAnchor(QLatin1String("left"), aiLeft)), mScaled(false), mScaledPixmapInvalidated(true), mAspectRatioMode(Qt::KeepAspectRatio), mTransformationMode(Qt::SmoothTransformation) { topLeft->setCoords(0, 1); bottomRight->setCoords(1, 0); setPen(Qt::NoPen); setSelectedPen(QPen(Qt::blue)); } QCPItemPixmap::~QCPItemPixmap() { } /*! Sets the pixmap that will be displayed. */ void QCPItemPixmap::setPixmap(const QPixmap &pixmap) { mPixmap = pixmap; mScaledPixmapInvalidated = true; if (mPixmap.isNull()) qDebug() << Q_FUNC_INFO << "pixmap is null"; } /*! Sets whether the pixmap will be scaled to fit the rectangle defined by the \a topLeft and \a bottomRight positions. */ void QCPItemPixmap::setScaled(bool scaled, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformationMode) { mScaled = scaled; mAspectRatioMode = aspectRatioMode; mTransformationMode = transformationMode; mScaledPixmapInvalidated = true; } /*! Sets the pen that will be used to draw a border around the pixmap. \see setSelectedPen, setBrush */ void QCPItemPixmap::setPen(const QPen &pen) { mPen = pen; } /*! Sets the pen that will be used to draw a border around the pixmap when selected \see setPen, setSelected */ void QCPItemPixmap::setSelectedPen(const QPen &pen) { mSelectedPen = pen; } /* inherits documentation from base class */ double QCPItemPixmap::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if (onlySelectable && !mSelectable) return -1; return rectDistance(getFinalRect(), pos, true); } /* inherits documentation from base class */ void QCPItemPixmap::draw(QCPPainter *painter) { bool flipHorz = false; bool flipVert = false; QRect rect = getFinalRect(&flipHorz, &flipVert); double clipPad = mainPen().style() == Qt::NoPen ? 0 : mainPen().widthF(); QRect boundingRect = rect.adjusted(-clipPad, -clipPad, clipPad, clipPad); if (boundingRect.intersects(clipRect())) { updateScaledPixmap(rect, flipHorz, flipVert); painter->drawPixmap(rect.topLeft(), mScaled ? mScaledPixmap : mPixmap); QPen pen = mainPen(); if (pen.style() != Qt::NoPen) { painter->setPen(pen); painter->setBrush(Qt::NoBrush); painter->drawRect(rect); } } } /* inherits documentation from base class */ QPointF QCPItemPixmap::anchorPixelPosition(int anchorId) const { bool flipHorz; bool flipVert; QRect rect = getFinalRect(&flipHorz, &flipVert); // we actually want denormal rects (negative width/height) here, so restore // the flipped state: if (flipHorz) rect.adjust(rect.width(), 0, -rect.width(), 0); if (flipVert) rect.adjust(0, rect.height(), 0, -rect.height()); switch (anchorId) { case aiTop: return (rect.topLeft() + rect.topRight()) * 0.5; case aiTopRight: return rect.topRight(); case aiRight: return (rect.topRight() + rect.bottomRight()) * 0.5; case aiBottom: return (rect.bottomLeft() + rect.bottomRight()) * 0.5; case aiBottomLeft: return rect.bottomLeft(); case aiLeft: return (rect.topLeft() + rect.bottomLeft()) * 0.5; ; } qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId; return QPointF(); } /*! \internal Creates the buffered scaled image (\a mScaledPixmap) to fit the specified \a finalRect. The parameters \a flipHorz and \a flipVert control whether the resulting image shall be flipped horizontally or vertically. (This is used when \a topLeft is further to the bottom/right than \a bottomRight.) This function only creates the scaled pixmap when the buffered pixmap has a different size than the expected result, so calling this function repeatedly, e.g. in the \ref draw function, does not cause expensive rescaling every time. If scaling is disabled, sets mScaledPixmap to a null QPixmap. */ void QCPItemPixmap::updateScaledPixmap(QRect finalRect, bool flipHorz, bool flipVert) { if (mPixmap.isNull()) return; if (mScaled) { #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED double devicePixelRatio = mPixmap.devicePixelRatio(); #else double devicePixelRatio = 1.0; #endif if (finalRect.isNull()) finalRect = getFinalRect(&flipHorz, &flipVert); if (mScaledPixmapInvalidated || finalRect.size() != mScaledPixmap.size() / devicePixelRatio) { mScaledPixmap = mPixmap.scaled(finalRect.size() * devicePixelRatio, mAspectRatioMode, mTransformationMode); if (flipHorz || flipVert) mScaledPixmap = QPixmap::fromImage(mScaledPixmap.toImage().mirrored(flipHorz, flipVert)); #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED mScaledPixmap.setDevicePixelRatio(devicePixelRatio); #endif } } else if (!mScaledPixmap.isNull()) mScaledPixmap = QPixmap(); mScaledPixmapInvalidated = false; } /*! \internal Returns the final (tight) rect the pixmap is drawn in, depending on the current item positions and scaling settings. The output parameters \a flippedHorz and \a flippedVert return whether the pixmap should be drawn flipped horizontally or vertically in the returned rect. (The returned rect itself is always normalized, i.e. the top left corner of the rect is actually further to the top/left than the bottom right corner). This is the case when the item position \a topLeft is further to the bottom/right than \a bottomRight. If scaling is disabled, returns a rect with size of the original pixmap and the top left corner aligned with the item position \a topLeft. The position \a bottomRight is ignored. */ QRect QCPItemPixmap::getFinalRect(bool *flippedHorz, bool *flippedVert) const { QRect result; bool flipHorz = false; bool flipVert = false; QPoint p1 = topLeft->pixelPosition().toPoint(); QPoint p2 = bottomRight->pixelPosition().toPoint(); if (p1 == p2) return QRect(p1, QSize(0, 0)); if (mScaled) { QSize newSize = QSize(p2.x() - p1.x(), p2.y() - p1.y()); QPoint topLeft = p1; if (newSize.width() < 0) { flipHorz = true; newSize.rwidth() *= -1; topLeft.setX(p2.x()); } if (newSize.height() < 0) { flipVert = true; newSize.rheight() *= -1; topLeft.setY(p2.y()); } QSize scaledSize = mPixmap.size(); #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED scaledSize /= mPixmap.devicePixelRatio(); scaledSize.scale(newSize * mPixmap.devicePixelRatio(), mAspectRatioMode); #else scaledSize.scale(newSize, mAspectRatioMode); #endif result = QRect(topLeft, scaledSize); } else { #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED result = QRect(p1, mPixmap.size() / mPixmap.devicePixelRatio()); #else result = QRect(p1, mPixmap.size()); #endif } if (flippedHorz) *flippedHorz = flipHorz; if (flippedVert) *flippedVert = flipVert; return result; } /*! \internal Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected and mSelectedPen when it is. */ QPen QCPItemPixmap::mainPen() const { return mSelected ? mSelectedPen : mPen; } /* end of 'src/items/item-pixmap.cpp' */ /* including file 'src/items/item-tracer.cpp', size 14624 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPItemTracer //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPItemTracer \brief Item that sticks to QCPGraph data points \image html QCPItemTracer.png "Tracer example. Blue dotted circles are anchors, solid blue discs are positions." The tracer can be connected with a QCPGraph via \ref setGraph. Then it will automatically adopt the coordinate axes of the graph and update its \a position to be on the graph's data. This means the key stays controllable via \ref setGraphKey, but the value will follow the graph data. If a QCPGraph is connected, note that setting the coordinates of the tracer item directly via \a position will have no effect because they will be overriden in the next redraw (this is when the coordinate update happens). If the specified key in \ref setGraphKey is outside the key bounds of the graph, the tracer will stay at the corresponding end of the graph. With \ref setInterpolating you may specify whether the tracer may only stay exactly on data points or whether it interpolates data points linearly, if given a key that lies between two data points of the graph. The tracer has different visual styles, see \ref setStyle. It is also possible to make the tracer have no own visual appearance (set the style to \ref tsNone), and just connect other item positions to the tracer \a position (used as an anchor) via \ref QCPItemPosition::setParentAnchor. \note The tracer position is only automatically updated upon redraws. So when the data of the graph changes and immediately afterwards (without a redraw) the position coordinates of the tracer are retrieved, they will not reflect the updated data of the graph. In this case \ref updatePosition must be called manually, prior to reading the tracer coordinates. */ /*! Creates a tracer item and sets default values. The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead. */ QCPItemTracer::QCPItemTracer(QCustomPlot *parentPlot) : QCPAbstractItem(parentPlot), position(createPosition(QLatin1String("position"))), mSize(6), mStyle(tsCrosshair), - mGraph(0), mGraphKey(0), mInterpolating(false) + mGraph(nullptr), mGraphKey(0), mInterpolating(false) { position->setCoords(0, 0); setBrush(Qt::NoBrush); setSelectedBrush(Qt::NoBrush); setPen(QPen(Qt::black)); setSelectedPen(QPen(Qt::blue, 2)); } QCPItemTracer::~QCPItemTracer() { } /*! Sets the pen that will be used to draw the line of the tracer \see setSelectedPen, setBrush */ void QCPItemTracer::setPen(const QPen &pen) { mPen = pen; } /*! Sets the pen that will be used to draw the line of the tracer when selected \see setPen, setSelected */ void QCPItemTracer::setSelectedPen(const QPen &pen) { mSelectedPen = pen; } /*! Sets the brush that will be used to draw any fills of the tracer \see setSelectedBrush, setPen */ void QCPItemTracer::setBrush(const QBrush &brush) { mBrush = brush; } /*! Sets the brush that will be used to draw any fills of the tracer, when selected. \see setBrush, setSelected */ void QCPItemTracer::setSelectedBrush(const QBrush &brush) { mSelectedBrush = brush; } /*! Sets the size of the tracer in pixels, if the style supports setting a size (e.g. \ref tsSquare does, \ref tsCrosshair does not). */ void QCPItemTracer::setSize(double size) { mSize = size; } /*! Sets the style/visual appearance of the tracer. If you only want to use the tracer \a position as an anchor for other items, set \a style to \ref tsNone. */ void QCPItemTracer::setStyle(QCPItemTracer::TracerStyle style) { mStyle = style; } /*! Sets the QCPGraph this tracer sticks to. The tracer \a position will be set to type QCPItemPosition::ptPlotCoords and the axes will be set to the axes of \a graph. To free the tracer from any graph, set \a graph to 0. The tracer \a position can then be placed freely like any other item position. This is the state the tracer will assume when its graph gets deleted while still attached to it. \see setGraphKey */ void QCPItemTracer::setGraph(QCPGraph *graph) { if (graph) { if (graph->parentPlot() == mParentPlot) { position->setType(QCPItemPosition::ptPlotCoords); position->setAxes(graph->keyAxis(), graph->valueAxis()); mGraph = graph; updatePosition(); } else qDebug() << Q_FUNC_INFO << "graph isn't in same QCustomPlot instance as this item"; } else { - mGraph = 0; + mGraph = nullptr; } } /*! Sets the key of the graph's data point the tracer will be positioned at. This is the only free coordinate of a tracer when attached to a graph. Depending on \ref setInterpolating, the tracer will be either positioned on the data point closest to \a key, or will stay exactly at \a key and interpolate the value linearly. \see setGraph, setInterpolating */ void QCPItemTracer::setGraphKey(double key) { mGraphKey = key; } /*! Sets whether the value of the graph's data points shall be interpolated, when positioning the tracer. If \a enabled is set to false and a key is given with \ref setGraphKey, the tracer is placed on the data point of the graph which is closest to the key, but which is not necessarily exactly there. If \a enabled is true, the tracer will be positioned exactly at the specified key, and the appropriate value will be interpolated from the graph's data points linearly. \see setGraph, setGraphKey */ void QCPItemTracer::setInterpolating(bool enabled) { mInterpolating = enabled; } /* inherits documentation from base class */ double QCPItemTracer::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if (onlySelectable && !mSelectable) return -1; QPointF center(position->pixelPosition()); double w = mSize / 2.0; QRect clip = clipRect(); switch (mStyle) { case tsNone: return -1; case tsPlus: { if (clipRect().intersects(QRectF(center - QPointF(w, w), center + QPointF(w, w)).toRect())) return qSqrt( qMin(QCPVector2D(pos).distanceSquaredToLine(center + QPointF(-w, 0), center + QPointF(w, 0)), QCPVector2D(pos).distanceSquaredToLine(center + QPointF(0, -w), center + QPointF(0, w)))); break; } case tsCrosshair: { return qSqrt(qMin(QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(clip.left(), center.y()), QCPVector2D(clip.right(), center.y())), QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(center.x(), clip.top()), QCPVector2D(center.x(), clip.bottom())))); } case tsCircle: { if (clip.intersects(QRectF(center - QPointF(w, w), center + QPointF(w, w)).toRect())) { // distance to border: double centerDist = QCPVector2D(center - pos).length(); double circleLine = w; double result = qAbs(centerDist - circleLine); // filled ellipse, allow click inside to count as hit: if (result > mParentPlot->selectionTolerance() * 0.99 && mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0) { if (centerDist <= circleLine) result = mParentPlot->selectionTolerance() * 0.99; } return result; } break; } case tsSquare: { if (clip.intersects(QRectF(center - QPointF(w, w), center + QPointF(w, w)).toRect())) { QRectF rect = QRectF(center - QPointF(w, w), center + QPointF(w, w)); bool filledRect = mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0; return rectDistance(rect, pos, filledRect); } break; } } return -1; } /* inherits documentation from base class */ void QCPItemTracer::draw(QCPPainter *painter) { updatePosition(); if (mStyle == tsNone) return; painter->setPen(mainPen()); painter->setBrush(mainBrush()); QPointF center(position->pixelPosition()); double w = mSize / 2.0; QRect clip = clipRect(); switch (mStyle) { case tsNone: return; case tsPlus: { if (clip.intersects(QRectF(center - QPointF(w, w), center + QPointF(w, w)).toRect())) { painter->drawLine(QLineF(center + QPointF(-w, 0), center + QPointF(w, 0))); painter->drawLine(QLineF(center + QPointF(0, -w), center + QPointF(0, w))); } break; } case tsCrosshair: { if (center.y() > clip.top() && center.y() < clip.bottom()) painter->drawLine(QLineF(clip.left(), center.y(), clip.right(), center.y())); if (center.x() > clip.left() && center.x() < clip.right()) painter->drawLine(QLineF(center.x(), clip.top(), center.x(), clip.bottom())); break; } case tsCircle: { if (clip.intersects(QRectF(center - QPointF(w, w), center + QPointF(w, w)).toRect())) painter->drawEllipse(center, w, w); break; } case tsSquare: { if (clip.intersects(QRectF(center - QPointF(w, w), center + QPointF(w, w)).toRect())) painter->drawRect(QRectF(center - QPointF(w, w), center + QPointF(w, w))); break; } } } /*! If the tracer is connected with a graph (\ref setGraph), this function updates the tracer's \a position to reside on the graph data, depending on the configured key (\ref setGraphKey). It is called automatically on every redraw and normally doesn't need to be called manually. One exception is when you want to read the tracer coordinates via \a position and are not sure that the graph's data (or the tracer key with \ref setGraphKey) hasn't changed since the last redraw. In that situation, call this function before accessing \a position, to make sure you don't get out-of-date coordinates. If there is no graph set on this tracer, this function does nothing. */ void QCPItemTracer::updatePosition() { if (mGraph) { if (mParentPlot->hasPlottable(mGraph)) { if (mGraph->data()->size() > 1) { QCPGraphDataContainer::const_iterator first = mGraph->data()->constBegin(); QCPGraphDataContainer::const_iterator last = mGraph->data()->constEnd() - 1; if (mGraphKey <= first->key) position->setCoords(first->key, first->value); else if (mGraphKey >= last->key) position->setCoords(last->key, last->value); else { QCPGraphDataContainer::const_iterator it = mGraph->data()->findBegin(mGraphKey); if (it != mGraph->data() ->constEnd()) // mGraphKey is not exactly on last iterator, but somewhere between iterators { QCPGraphDataContainer::const_iterator prevIt = it; ++it; // won't advance to constEnd because we handled that case (mGraphKey >= last->key) before if (mInterpolating) { // interpolate between iterators around mGraphKey: double slope = 0; if (!qFuzzyCompare((double)it->key, (double)prevIt->key)) slope = (it->value - prevIt->value) / (it->key - prevIt->key); position->setCoords(mGraphKey, (mGraphKey - prevIt->key) * slope + prevIt->value); } else { // find iterator with key closest to mGraphKey: if (mGraphKey < (prevIt->key + it->key) * 0.5) position->setCoords(prevIt->key, prevIt->value); else position->setCoords(it->key, it->value); } } else // mGraphKey is exactly on last iterator (should actually be caught when comparing first/last keys, but this is a failsafe for fp uncertainty) position->setCoords(it->key, it->value); } } else if (mGraph->data()->size() == 1) { QCPGraphDataContainer::const_iterator it = mGraph->data()->constBegin(); position->setCoords(it->key, it->value); } else qDebug() << Q_FUNC_INFO << "graph has no data"; } else qDebug() << Q_FUNC_INFO << "graph not contained in QCustomPlot instance (anymore)"; } } /*! \internal Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected and mSelectedPen when it is. */ QPen QCPItemTracer::mainPen() const { return mSelected ? mSelectedPen : mPen; } /*! \internal Returns the brush that should be used for drawing fills of the item. Returns mBrush when the item is not selected and mSelectedBrush when it is. */ QBrush QCPItemTracer::mainBrush() const { return mSelected ? mSelectedBrush : mBrush; } /* end of 'src/items/item-tracer.cpp' */ /* including file 'src/items/item-bracket.cpp', size 10687 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPItemBracket //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPItemBracket \brief A bracket for referencing/highlighting certain parts in the plot. \image html QCPItemBracket.png "Bracket example. Blue dotted circles are anchors, solid blue discs are positions." It has two positions, \a left and \a right, which define the span of the bracket. If \a left is actually farther to the left than \a right, the bracket is opened to the bottom, as shown in the example image. The bracket supports multiple styles via \ref setStyle. The length, i.e. how far the bracket stretches away from the embraced span, can be controlled with \ref setLength. \image html QCPItemBracket-length.png
Demonstrating the effect of different values for \ref setLength, for styles \ref bsCalligraphic and \ref bsSquare. Anchors and positions are displayed for reference.
It provides an anchor \a center, to allow connection of other items, e.g. an arrow (QCPItemLine or QCPItemCurve) or a text label (QCPItemText), to the bracket. */ /*! Creates a bracket item and sets default values. The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead. */ QCPItemBracket::QCPItemBracket(QCustomPlot *parentPlot) : QCPAbstractItem(parentPlot), left(createPosition(QLatin1String("left"))), right(createPosition(QLatin1String("right"))), center(createAnchor(QLatin1String("center"), aiCenter)), mLength(8), mStyle(bsCalligraphic) { left->setCoords(0, 0); right->setCoords(1, 1); setPen(QPen(Qt::black)); setSelectedPen(QPen(Qt::blue, 2)); } QCPItemBracket::~QCPItemBracket() { } /*! Sets the pen that will be used to draw the bracket. Note that when the style is \ref bsCalligraphic, only the color will be taken from the pen, the stroke and width are ignored. To change the apparent stroke width of a calligraphic bracket, use \ref setLength, which has a similar effect. \see setSelectedPen */ void QCPItemBracket::setPen(const QPen &pen) { mPen = pen; } /*! Sets the pen that will be used to draw the bracket when selected \see setPen, setSelected */ void QCPItemBracket::setSelectedPen(const QPen &pen) { mSelectedPen = pen; } /*! Sets the \a length in pixels how far the bracket extends in the direction towards the embraced span of the bracket (i.e. perpendicular to the left-right-direction) \image html QCPItemBracket-length.png
Demonstrating the effect of different values for \ref setLength, for styles \ref bsCalligraphic and \ref bsSquare. Anchors and positions are displayed for reference.
*/ void QCPItemBracket::setLength(double length) { mLength = length; } /*! Sets the style of the bracket, i.e. the shape/visual appearance. \see setPen */ void QCPItemBracket::setStyle(QCPItemBracket::BracketStyle style) { mStyle = style; } /* inherits documentation from base class */ double QCPItemBracket::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { Q_UNUSED(details) if (onlySelectable && !mSelectable) return -1; QCPVector2D p(pos); QCPVector2D leftVec(left->pixelPosition()); QCPVector2D rightVec(right->pixelPosition()); if (leftVec.toPoint() == rightVec.toPoint()) return -1; QCPVector2D widthVec = (rightVec - leftVec) * 0.5; QCPVector2D lengthVec = widthVec.perpendicular().normalized() * mLength; QCPVector2D centerVec = (rightVec + leftVec) * 0.5 - lengthVec; switch (mStyle) { case QCPItemBracket::bsSquare: case QCPItemBracket::bsRound: { double a = p.distanceSquaredToLine(centerVec - widthVec, centerVec + widthVec); double b = p.distanceSquaredToLine(centerVec - widthVec + lengthVec, centerVec - widthVec); double c = p.distanceSquaredToLine(centerVec + widthVec + lengthVec, centerVec + widthVec); return qSqrt(qMin(qMin(a, b), c)); } case QCPItemBracket::bsCurly: case QCPItemBracket::bsCalligraphic: { double a = p.distanceSquaredToLine(centerVec - widthVec * 0.75 + lengthVec * 0.15, centerVec + lengthVec * 0.3); double b = p.distanceSquaredToLine(centerVec - widthVec + lengthVec * 0.7, centerVec - widthVec * 0.75 + lengthVec * 0.15); double c = p.distanceSquaredToLine(centerVec + widthVec * 0.75 + lengthVec * 0.15, centerVec + lengthVec * 0.3); double d = p.distanceSquaredToLine(centerVec + widthVec + lengthVec * 0.7, centerVec + widthVec * 0.75 + lengthVec * 0.15); return qSqrt(qMin(qMin(a, b), qMin(c, d))); } } return -1; } /* inherits documentation from base class */ void QCPItemBracket::draw(QCPPainter *painter) { QCPVector2D leftVec(left->pixelPosition()); QCPVector2D rightVec(right->pixelPosition()); if (leftVec.toPoint() == rightVec.toPoint()) return; QCPVector2D widthVec = (rightVec - leftVec) * 0.5; QCPVector2D lengthVec = widthVec.perpendicular().normalized() * mLength; QCPVector2D centerVec = (rightVec + leftVec) * 0.5 - lengthVec; QPolygon boundingPoly; boundingPoly << leftVec.toPoint() << rightVec.toPoint() << (rightVec - lengthVec).toPoint() << (leftVec - lengthVec).toPoint(); QRect clip = clipRect().adjusted(-mainPen().widthF(), -mainPen().widthF(), mainPen().widthF(), mainPen().widthF()); if (clip.intersects(boundingPoly.boundingRect())) { painter->setPen(mainPen()); switch (mStyle) { case bsSquare: { painter->drawLine((centerVec + widthVec).toPointF(), (centerVec - widthVec).toPointF()); painter->drawLine((centerVec + widthVec).toPointF(), (centerVec + widthVec + lengthVec).toPointF()); painter->drawLine((centerVec - widthVec).toPointF(), (centerVec - widthVec + lengthVec).toPointF()); break; } case bsRound: { painter->setBrush(Qt::NoBrush); QPainterPath path; path.moveTo((centerVec + widthVec + lengthVec).toPointF()); path.cubicTo((centerVec + widthVec).toPointF(), (centerVec + widthVec).toPointF(), centerVec.toPointF()); path.cubicTo((centerVec - widthVec).toPointF(), (centerVec - widthVec).toPointF(), (centerVec - widthVec + lengthVec).toPointF()); painter->drawPath(path); break; } case bsCurly: { painter->setBrush(Qt::NoBrush); QPainterPath path; path.moveTo((centerVec + widthVec + lengthVec).toPointF()); path.cubicTo((centerVec + widthVec - lengthVec * 0.8).toPointF(), (centerVec + 0.4 * widthVec + lengthVec).toPointF(), centerVec.toPointF()); path.cubicTo((centerVec - 0.4 * widthVec + lengthVec).toPointF(), (centerVec - widthVec - lengthVec * 0.8).toPointF(), (centerVec - widthVec + lengthVec).toPointF()); painter->drawPath(path); break; } case bsCalligraphic: { painter->setPen(Qt::NoPen); painter->setBrush(QBrush(mainPen().color())); QPainterPath path; path.moveTo((centerVec + widthVec + lengthVec).toPointF()); path.cubicTo((centerVec + widthVec - lengthVec * 0.8).toPointF(), (centerVec + 0.4 * widthVec + 0.8 * lengthVec).toPointF(), centerVec.toPointF()); path.cubicTo((centerVec - 0.4 * widthVec + 0.8 * lengthVec).toPointF(), (centerVec - widthVec - lengthVec * 0.8).toPointF(), (centerVec - widthVec + lengthVec).toPointF()); path.cubicTo((centerVec - widthVec - lengthVec * 0.5).toPointF(), (centerVec - 0.2 * widthVec + 1.2 * lengthVec).toPointF(), (centerVec + lengthVec * 0.2).toPointF()); path.cubicTo((centerVec + 0.2 * widthVec + 1.2 * lengthVec).toPointF(), (centerVec + widthVec - lengthVec * 0.5).toPointF(), (centerVec + widthVec + lengthVec).toPointF()); painter->drawPath(path); break; } } } } /* inherits documentation from base class */ QPointF QCPItemBracket::anchorPixelPosition(int anchorId) const { QCPVector2D leftVec(left->pixelPosition()); QCPVector2D rightVec(right->pixelPosition()); if (leftVec.toPoint() == rightVec.toPoint()) return leftVec.toPointF(); QCPVector2D widthVec = (rightVec - leftVec) * 0.5; QCPVector2D lengthVec = widthVec.perpendicular().normalized() * mLength; QCPVector2D centerVec = (rightVec + leftVec) * 0.5 - lengthVec; switch (anchorId) { case aiCenter: return centerVec.toPointF(); } qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId; return QPointF(); } /*! \internal Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected and mSelectedPen when it is. */ QPen QCPItemBracket::mainPen() const { return mSelected ? mSelectedPen : mPen; } /* end of 'src/items/item-bracket.cpp' */ diff --git a/kstars/auxiliary/qcustomplot.h b/kstars/auxiliary/qcustomplot.h index 390461728..0dba2116e 100644 --- a/kstars/auxiliary/qcustomplot.h +++ b/kstars/auxiliary/qcustomplot.h @@ -1,7030 +1,7030 @@ /*************************************************************************** ** ** ** QCustomPlot, an easy to use, modern plotting widget for Qt ** ** Copyright (C) 2011-2016 Emanuel Eichhammer ** ** ** ** 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 3 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, see http://www.gnu.org/licenses/. ** ** ** **************************************************************************** ** Author: Emanuel Eichhammer ** ** Website/Contact: http://www.qcustomplot.com/ ** ** Date: 13.09.16 ** ** Version: 2.0.0-beta ** ****************************************************************************/ #ifndef QCUSTOMPLOT_H #define QCUSTOMPLOT_H #include // some Qt version/configuration dependent macros to include or exclude certain code paths: #ifdef QCUSTOMPLOT_USE_OPENGL #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #define QCP_OPENGL_PBUFFER #else #define QCP_OPENGL_FBO #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 3, 0) #define QCP_OPENGL_OFFSCREENSURFACE #endif #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) #define QCP_DEVICEPIXELRATIO_SUPPORTED #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef QCP_OPENGL_FBO #include #include #ifdef QCP_OPENGL_OFFSCREENSURFACE #include #else #include #endif #endif #ifdef QCP_OPENGL_PBUFFER #include #endif #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #include #include #include #include #else #include #include #include #endif class QCPPainter; class QCustomPlot; class QCPLayerable; class QCPLayoutElement; class QCPLayout; class QCPAxis; class QCPAxisRect; class QCPAxisPainterPrivate; class QCPAbstractPlottable; class QCPGraph; class QCPAbstractItem; class QCPPlottableInterface1D; class QCPLegend; class QCPItemPosition; class QCPLayer; class QCPAbstractLegendItem; class QCPSelectionRect; class QCPColorMap; class QCPColorScale; class QCPBars; /* including file 'src/global.h', size 16131 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ // decl definitions for shared library compilation/usage: #if defined(QCUSTOMPLOT_COMPILE_LIBRARY) #define QCP_LIB_DECL Q_DECL_EXPORT #elif defined(QCUSTOMPLOT_USE_LIBRARY) #define QCP_LIB_DECL Q_DECL_IMPORT #else #define QCP_LIB_DECL #endif // define empty macro for override if it doesn't exist (Qt < 5) #ifndef override #define override #endif /*! The QCP Namespace contains general enums, QFlags and functions used throughout the QCustomPlot library. It provides QMetaObject-based reflection of its enums and flags via \a QCP::staticMetaObject. */ #ifndef Q_MOC_RUN namespace QCP { #else class QCP // when in moc-run, make it look like a class, so we get Q_GADGET, Q_ENUM/Q_FLAGS features in namespace { Q_GADGET Q_ENUM(ExportPen) Q_ENUM(ResolutionUnit) Q_ENUM(SignDomain) Q_ENUM(MarginSide) Q_FLAGS(MarginSides) Q_ENUM(AntialiasedElement) Q_FLAGS(AntialiasedElements) Q_ENUM(PlottingHint) Q_FLAGS(PlottingHints) Q_ENUM(Interaction) Q_FLAGS(Interactions) Q_ENUM(SelectionRectMode) Q_ENUM(SelectionType) public: #endif /*! Defines the different units in which the image resolution can be specified in the export functions. \see QCustomPlot::savePng, QCustomPlot::saveJpg, QCustomPlot::saveBmp, QCustomPlot::saveRastered */ enum ResolutionUnit { ruDotsPerMeter ///< Resolution is given in dots per meter (dpm) , ruDotsPerCentimeter ///< Resolution is given in dots per centimeter (dpcm) , ruDotsPerInch ///< Resolution is given in dots per inch (DPI/PPI) }; /*! Defines how cosmetic pens (pens with numerical width 0) are handled during export. \see QCustomPlot::savePdf */ enum ExportPen { epNoCosmetic ///< Cosmetic pens are converted to pens with pixel width 1 when exporting , epAllowCosmetic ///< Cosmetic pens are exported normally (e.g. in PDF exports, cosmetic pens always appear as 1 pixel on screen, independent of viewer zoom level) }; /*! Represents negative and positive sign domain, e.g. for passing to \ref QCPAbstractPlottable::getKeyRange and \ref QCPAbstractPlottable::getValueRange. This is primarily needed when working with logarithmic axis scales, since only one of the sign domains can be visible at a time. */ enum SignDomain { sdNegative ///< The negative sign domain, i.e. numbers smaller than zero , sdBoth ///< Both sign domains, including zero, i.e. all numbers , sdPositive ///< The positive sign domain, i.e. numbers greater than zero }; /*! Defines the sides of a rectangular entity to which margins can be applied. \see QCPLayoutElement::setAutoMargins, QCPAxisRect::setAutoMargins */ enum MarginSide { msLeft = 0x01 ///< 0x01 left margin , msRight = 0x02 ///< 0x02 right margin , msTop = 0x04 ///< 0x04 top margin , msBottom = 0x08 ///< 0x08 bottom margin , msAll = 0xFF ///< 0xFF all margins , msNone = 0x00 ///< 0x00 no margin }; Q_DECLARE_FLAGS(MarginSides, MarginSide) /*! Defines what objects of a plot can be forcibly drawn antialiased/not antialiased. If an object is neither forcibly drawn antialiased nor forcibly drawn not antialiased, it is up to the respective element how it is drawn. Typically it provides a \a setAntialiased function for this. \c AntialiasedElements is a flag of or-combined elements of this enum type. \see QCustomPlot::setAntialiasedElements, QCustomPlot::setNotAntialiasedElements */ enum AntialiasedElement { aeAxes = 0x0001 ///< 0x0001 Axis base line and tick marks , aeGrid = 0x0002 ///< 0x0002 Grid lines , aeSubGrid = 0x0004 ///< 0x0004 Sub grid lines , aeLegend = 0x0008 ///< 0x0008 Legend box , aeLegendItems = 0x0010 ///< 0x0010 Legend items , aePlottables = 0x0020 ///< 0x0020 Main lines of plottables , aeItems = 0x0040 ///< 0x0040 Main lines of items , aeScatters = 0x0080 ///< 0x0080 Scatter symbols of plottables (excluding scatter symbols of type ssPixmap) , aeFills = 0x0100 ///< 0x0100 Borders of fills (e.g. under or between graphs) , aeZeroLine = 0x0200 ///< 0x0200 Zero-lines, see \ref QCPGrid::setZeroLinePen , aeOther = 0x8000 ///< 0x8000 Other elements that don't fit into any of the existing categories , aeAll = 0xFFFF ///< 0xFFFF All elements , aeNone = 0x0000 ///< 0x0000 No elements }; Q_DECLARE_FLAGS(AntialiasedElements, AntialiasedElement) /*! Defines plotting hints that control various aspects of the quality and speed of plotting. \see QCustomPlot::setPlottingHints */ enum PlottingHint { phNone = 0x000 ///< 0x000 No hints are set , phFastPolylines = 0x001 ///< 0x001 Graph/Curve lines are drawn with a faster method. This reduces the quality especially of the line segment ///< joins, thus is most effective for pen sizes larger than 1. It is only used for solid line pens. , phImmediateRefresh = 0x002 ///< 0x002 causes an immediate repaint() instead of a soft update() when QCustomPlot::replot() is called with parameter \ref QCustomPlot::rpRefreshHint. ///< This is set by default to prevent the plot from freezing on fast consecutive replots (e.g. user drags ranges with mouse). , phCacheLabels = 0x004 ///< 0x004 axis (tick) labels will be cached as pixmaps, increasing replot performance. }; Q_DECLARE_FLAGS(PlottingHints, PlottingHint) /*! Defines the mouse interactions possible with QCustomPlot. \c Interactions is a flag of or-combined elements of this enum type. \see QCustomPlot::setInteractions */ enum Interaction { iRangeDrag = 0x001 ///< 0x001 Axis ranges are draggable (see \ref QCPAxisRect::setRangeDrag, \ref QCPAxisRect::setRangeDragAxes) , iRangeZoom = 0x002 ///< 0x002 Axis ranges are zoomable with the mouse wheel (see \ref QCPAxisRect::setRangeZoom, \ref QCPAxisRect::setRangeZoomAxes) , iMultiSelect = 0x004 ///< 0x004 The user can select multiple objects by holding the modifier set by \ref QCustomPlot::setMultiSelectModifier while clicking , iSelectPlottables = 0x008 ///< 0x008 Plottables are selectable (e.g. graphs, curves, bars,... see QCPAbstractPlottable) , iSelectAxes = 0x010 ///< 0x010 Axes are selectable (or parts of them, see QCPAxis::setSelectableParts) , iSelectLegend = 0x020 ///< 0x020 Legends are selectable (or their child items, see QCPLegend::setSelectableParts) , iSelectItems = 0x040 ///< 0x040 Items are selectable (Rectangles, Arrows, Textitems, etc. see \ref QCPAbstractItem) , iSelectOther = 0x080 ///< 0x080 All other objects are selectable (e.g. your own derived layerables, other layout elements,...) }; Q_DECLARE_FLAGS(Interactions, Interaction) /*! Defines the behaviour of the selection rect. \see QCustomPlot::setSelectionRectMode, QCustomPlot::selectionRect, QCPSelectionRect */ enum SelectionRectMode { srmNone ///< The selection rect is disabled, and all mouse events are forwarded to the underlying objects, e.g. for axis range dragging , srmZoom ///< When dragging the mouse, a selection rect becomes active. Upon releasing, the axes that are currently set as range zoom axes (\ref QCPAxisRect::setRangeZoomAxes) will have their ranges zoomed accordingly. , srmSelect ///< When dragging the mouse, a selection rect becomes active. Upon releasing, plottable data points that were within the selection rect are selected, if the plottable's selectability setting permits. (See \ref dataselection "data selection mechanism" for details.) , srmCustom ///< When dragging the mouse, a selection rect becomes active. It is the programmer's responsibility to connect according slots to the selection rect's signals (e.g. \ref QCPSelectionRect::accepted) in order to process the user interaction. }; /*! Defines the different ways a plottable can be selected. These images show the effect of the different selection types, when the indicated selection rect was dragged:
\image html selectiontype-none.png stNone \image html selectiontype-whole.png stWhole \image html selectiontype-singledata.png stSingleData \image html selectiontype-datarange.png stDataRange \image html selectiontype-multipledataranges.png stMultipleDataRanges
\see QCPAbstractPlottable::setSelectable, QCPDataSelection::enforceType */ enum SelectionType { stNone ///< The plottable is not selectable , stWhole ///< Selection behaves like \ref stMultipleDataRanges, but if there are any data points selected, the entire plottable is drawn as selected. , stSingleData ///< One individual data point can be selected at a time , stDataRange ///< Multiple contiguous data points (a data range) can be selected , stMultipleDataRanges ///< Any combination of data points/ranges can be selected }; /*! \internal Returns whether the specified \a value is considered an invalid data value for plottables (i.e. is \e nan or \e +/-inf). This function is used to check data validity upon replots, when the compiler flag \c QCUSTOMPLOT_CHECK_DATA is set. */ inline bool isInvalidData(double value) { return qIsNaN(value) || qIsInf(value); } /*! \internal \overload Checks two arguments instead of one. */ inline bool isInvalidData(double value1, double value2) { return isInvalidData(value1) || isInvalidData(value2); } /*! \internal Sets the specified \a side of \a margins to \a value \see getMarginValue */ inline void setMarginValue(QMargins &margins, QCP::MarginSide side, int value) { switch (side) { case QCP::msLeft: margins.setLeft(value); break; case QCP::msRight: margins.setRight(value); break; case QCP::msTop: margins.setTop(value); break; case QCP::msBottom: margins.setBottom(value); break; case QCP::msAll: margins = QMargins(value, value, value, value); break; default: break; } } /*! \internal Returns the value of the specified \a side of \a margins. If \a side is \ref QCP::msNone or \ref QCP::msAll, returns 0. \see setMarginValue */ inline int getMarginValue(const QMargins &margins, QCP::MarginSide side) { switch (side) { case QCP::msLeft: return margins.left(); case QCP::msRight: return margins.right(); case QCP::msTop: return margins.top(); case QCP::msBottom: return margins.bottom(); default: break; } return 0; } extern const QMetaObject staticMetaObject; // in moc-run we create a static meta object for QCP "fake" object. This line is the link to it via QCP::staticMetaObject in normal operation as namespace } // end of namespace QCP Q_DECLARE_OPERATORS_FOR_FLAGS(QCP::AntialiasedElements) Q_DECLARE_OPERATORS_FOR_FLAGS(QCP::PlottingHints) Q_DECLARE_OPERATORS_FOR_FLAGS(QCP::MarginSides) Q_DECLARE_OPERATORS_FOR_FLAGS(QCP::Interactions) Q_DECLARE_METATYPE(QCP::ExportPen) Q_DECLARE_METATYPE(QCP::ResolutionUnit) Q_DECLARE_METATYPE(QCP::SignDomain) Q_DECLARE_METATYPE(QCP::MarginSide) Q_DECLARE_METATYPE(QCP::AntialiasedElement) Q_DECLARE_METATYPE(QCP::PlottingHint) Q_DECLARE_METATYPE(QCP::Interaction) Q_DECLARE_METATYPE(QCP::SelectionRectMode) Q_DECLARE_METATYPE(QCP::SelectionType) /* end of 'src/global.h' */ /* including file 'src/vector2d.h', size 4928 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPVector2D { public: QCPVector2D(); QCPVector2D(double x, double y); QCPVector2D(const QPoint &point); QCPVector2D(const QPointF &point); // getters: double x() const { return mX; } double y() const { return mY; } double &rx() { return mX; } double &ry() { return mY; } // setters: void setX(double x) { mX = x; } void setY(double y) { mY = y; } // non-virtual methods: double length() const { return qSqrt(mX * mX + mY * mY); } double lengthSquared() const { return mX * mX + mY * mY; } QPoint toPoint() const { return QPoint(mX, mY); } QPointF toPointF() const { return QPointF(mX, mY); } bool isNull() const { return qIsNull(mX) && qIsNull(mY); } void normalize(); QCPVector2D normalized() const; QCPVector2D perpendicular() const { return QCPVector2D(-mY, mX); } double dot(const QCPVector2D &vec) const { return mX * vec.mX + mY * vec.mY; } double distanceSquaredToLine(const QCPVector2D &start, const QCPVector2D &end) const; double distanceSquaredToLine(const QLineF &line) const; double distanceToStraightLine(const QCPVector2D &base, const QCPVector2D &direction) const; QCPVector2D &operator*=(double factor); QCPVector2D &operator/=(double divisor); QCPVector2D &operator+=(const QCPVector2D &vector); QCPVector2D &operator-=(const QCPVector2D &vector); private: // property members: double mX, mY; friend inline const QCPVector2D operator*(double factor, const QCPVector2D &vec); friend inline const QCPVector2D operator*(const QCPVector2D &vec, double factor); friend inline const QCPVector2D operator/(const QCPVector2D &vec, double divisor); friend inline const QCPVector2D operator+(const QCPVector2D &vec1, const QCPVector2D &vec2); friend inline const QCPVector2D operator-(const QCPVector2D &vec1, const QCPVector2D &vec2); friend inline const QCPVector2D operator-(const QCPVector2D &vec); }; Q_DECLARE_TYPEINFO(QCPVector2D, Q_MOVABLE_TYPE); inline const QCPVector2D operator*(double factor, const QCPVector2D &vec) { return QCPVector2D(vec.mX * factor, vec.mY * factor); } inline const QCPVector2D operator*(const QCPVector2D &vec, double factor) { return QCPVector2D(vec.mX * factor, vec.mY * factor); } inline const QCPVector2D operator/(const QCPVector2D &vec, double divisor) { return QCPVector2D(vec.mX / divisor, vec.mY / divisor); } inline const QCPVector2D operator+(const QCPVector2D &vec1, const QCPVector2D &vec2) { return QCPVector2D(vec1.mX + vec2.mX, vec1.mY + vec2.mY); } inline const QCPVector2D operator-(const QCPVector2D &vec1, const QCPVector2D &vec2) { return QCPVector2D(vec1.mX - vec2.mX, vec1.mY - vec2.mY); } inline const QCPVector2D operator-(const QCPVector2D &vec) { return QCPVector2D(-vec.mX, -vec.mY); } /*! \relates QCPVector2D Prints \a vec in a human readable format to the qDebug output. */ inline QDebug operator<<(QDebug d, const QCPVector2D &vec) { d.nospace() << "QCPVector2D(" << vec.x() << ", " << vec.y() << ")"; return d.space(); } /* end of 'src/vector2d.h' */ /* including file 'src/painter.h', size 4035 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPPainter : public QPainter { Q_GADGET public: /*! Defines special modes the painter can operate in. They disable or enable certain subsets of features/fixes/workarounds, depending on whether they are wanted on the respective output device. */ enum PainterMode { pmDefault = 0x00 ///< 0x00 Default mode for painting on screen devices , pmVectorized = 0x01 ///< 0x01 Mode for vectorized painting (e.g. PDF export). For example, this prevents some antialiasing fixes. , pmNoCaching = 0x02 ///< 0x02 Mode for all sorts of exports (e.g. PNG, PDF,...). For example, this prevents using cached pixmap labels , pmNonCosmetic = 0x04 ///< 0x04 Turns pen widths 0 to 1, i.e. disables cosmetic pens. (A cosmetic pen is always drawn with width 1 pixel in the vector image/pdf viewer, independent of zoom.) }; Q_ENUM(PainterMode) Q_FLAGS(PainterModes) Q_DECLARE_FLAGS(PainterModes, PainterMode) QCPPainter(); explicit QCPPainter(QPaintDevice *device); // getters: bool antialiasing() const { return testRenderHint(QPainter::Antialiasing); } PainterModes modes() const { return mModes; } // setters: void setAntialiasing(bool enabled); void setMode(PainterMode mode, bool enabled = true); void setModes(PainterModes modes); // methods hiding non-virtual base class functions (QPainter bug workarounds): bool begin(QPaintDevice *device); void setPen(const QPen &pen); void setPen(const QColor &color); void setPen(Qt::PenStyle penStyle); void drawLine(const QLineF &line); void drawLine(const QPointF &p1, const QPointF &p2) { drawLine(QLineF(p1, p2)); } void save(); void restore(); // non-virtual methods: void makeNonCosmetic(); protected: // property members: PainterModes mModes; bool mIsAntialiasing; // non-property members: QStack mAntialiasingStack; }; Q_DECLARE_OPERATORS_FOR_FLAGS(QCPPainter::PainterModes) Q_DECLARE_METATYPE(QCPPainter::PainterMode) /* end of 'src/painter.h' */ /* including file 'src/paintbuffer.h', size 4958 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPAbstractPaintBuffer { public: explicit QCPAbstractPaintBuffer(const QSize &size, double devicePixelRatio); virtual ~QCPAbstractPaintBuffer(); // getters: QSize size() const { return mSize; } bool invalidated() const { return mInvalidated; } double devicePixelRatio() const { return mDevicePixelRatio; } // setters: void setSize(const QSize &size); void setInvalidated(bool invalidated = true); void setDevicePixelRatio(double ratio); // introduced virtual methods: virtual QCPPainter *startPainting() = 0; virtual void donePainting() {} virtual void draw(QCPPainter *painter) const = 0; virtual void clear(const QColor &color) = 0; protected: // property members: QSize mSize; double mDevicePixelRatio; // non-property members: bool mInvalidated; // introduced virtual methods: virtual void reallocateBuffer() = 0; }; class QCP_LIB_DECL QCPPaintBufferPixmap : public QCPAbstractPaintBuffer { public: explicit QCPPaintBufferPixmap(const QSize &size, double devicePixelRatio); ~QCPPaintBufferPixmap() override; // reimplemented virtual methods: QCPPainter *startPainting() override; void draw(QCPPainter *painter) const override; void clear(const QColor &color) override; protected: // non-property members: QPixmap mBuffer; // reimplemented virtual methods: void reallocateBuffer() override; }; #ifdef QCP_OPENGL_PBUFFER class QCP_LIB_DECL QCPPaintBufferGlPbuffer : public QCPAbstractPaintBuffer { public: explicit QCPPaintBufferGlPbuffer(const QSize &size, double devicePixelRatio, int multisamples); virtual ~QCPPaintBufferGlPbuffer(); // reimplemented virtual methods: virtual QCPPainter *startPainting() override; virtual void draw(QCPPainter *painter) const override; void clear(const QColor &color) override; protected: // non-property members: QGLPixelBuffer *mGlPBuffer; int mMultisamples; // reimplemented virtual methods: virtual void reallocateBuffer() override; }; #endif // QCP_OPENGL_PBUFFER #ifdef QCP_OPENGL_FBO class QCP_LIB_DECL QCPPaintBufferGlFbo : public QCPAbstractPaintBuffer { public: explicit QCPPaintBufferGlFbo(const QSize &size, double devicePixelRatio, QWeakPointer glContext, QWeakPointer glPaintDevice); virtual ~QCPPaintBufferGlFbo(); // reimplemented virtual methods: virtual QCPPainter *startPainting() override; virtual void donePainting() override; virtual void draw(QCPPainter *painter) const override; void clear(const QColor &color) override; protected: // non-property members: QWeakPointer mGlContext; QWeakPointer mGlPaintDevice; QOpenGLFramebufferObject *mGlFrameBuffer; // reimplemented virtual methods: virtual void reallocateBuffer() override; }; #endif // QCP_OPENGL_FBO /* end of 'src/paintbuffer.h' */ /* including file 'src/layer.h', size 6885 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPLayer : public QObject { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QCustomPlot *parentPlot READ parentPlot) Q_PROPERTY(QString name READ name) Q_PROPERTY(int index READ index) Q_PROPERTY(QList children READ children) Q_PROPERTY(bool visible READ visible WRITE setVisible) Q_PROPERTY(LayerMode mode READ mode WRITE setMode) /// \endcond public: /*! Defines the different rendering modes of a layer. Depending on the mode, certain layers can be replotted individually, without the need to replot (possibly complex) layerables on other layers. \see setMode */ enum LayerMode { lmLogical ///< Layer is used only for rendering order, and shares paint buffer with all other adjacent logical layers. , lmBuffered ///< Layer has its own paint buffer and may be replotted individually (see \ref replot). }; Q_ENUM(LayerMode) QCPLayer(QCustomPlot *parentPlot, const QString &layerName); ~QCPLayer() override; // getters: QCustomPlot *parentPlot() const { return mParentPlot; } QString name() const { return mName; } int index() const { return mIndex; } QList children() const { return mChildren; } bool visible() const { return mVisible; } LayerMode mode() const { return mMode; } // setters: void setVisible(bool visible); void setMode(LayerMode mode); // non-virtual methods: void replot(); protected: // property members: QCustomPlot *mParentPlot; QString mName; int mIndex; QList mChildren; bool mVisible; LayerMode mMode; // non-property members: QWeakPointer mPaintBuffer; // non-virtual methods: void draw(QCPPainter *painter); void drawToPaintBuffer(); void addChild(QCPLayerable *layerable, bool prepend); void removeChild(QCPLayerable *layerable); private: Q_DISABLE_COPY(QCPLayer) friend class QCustomPlot; friend class QCPLayerable; }; Q_DECLARE_METATYPE(QCPLayer::LayerMode) class QCP_LIB_DECL QCPLayerable : public QObject { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(bool visible READ visible WRITE setVisible) Q_PROPERTY(QCustomPlot *parentPlot READ parentPlot) Q_PROPERTY(QCPLayerable *parentLayerable READ parentLayerable) Q_PROPERTY(QCPLayer *layer READ layer WRITE setLayer NOTIFY layerChanged) Q_PROPERTY(bool antialiased READ antialiased WRITE setAntialiased) /// \endcond public: - QCPLayerable(QCustomPlot *plot, QString targetLayer = QString(), QCPLayerable *parentLayerable = 0); + QCPLayerable(QCustomPlot *plot, QString targetLayer = QString(), QCPLayerable *parentLayerable = nullptr); ~QCPLayerable() override; // getters: bool visible() const { return mVisible; } QCustomPlot *parentPlot() const { return mParentPlot; } QCPLayerable *parentLayerable() const { return mParentLayerable.data(); } QCPLayer *layer() const { return mLayer; } bool antialiased() const { return mAntialiased; } // setters: void setVisible(bool on); Q_SLOT bool setLayer(QCPLayer *layer); bool setLayer(const QString &layerName); void setAntialiased(bool enabled); // introduced virtual methods: - virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const; + virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const; // non-property methods: bool realVisibility() const; signals: void layerChanged(QCPLayer *newLayer); protected: // property members: bool mVisible; QCustomPlot *mParentPlot; QPointer mParentLayerable; QCPLayer *mLayer; bool mAntialiased; // introduced virtual methods: virtual void parentPlotInitialized(QCustomPlot *parentPlot); virtual QCP::Interaction selectionCategory() const; virtual QRect clipRect() const; virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const = 0; virtual void draw(QCPPainter *painter) = 0; // selection events: virtual void selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged); virtual void deselectEvent(bool *selectionStateChanged); // low-level mouse events: virtual void mousePressEvent(QMouseEvent *event, const QVariant &details); virtual void mouseMoveEvent(QMouseEvent *event, const QPointF &startPos); virtual void mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos); virtual void mouseDoubleClickEvent(QMouseEvent *event, const QVariant &details); virtual void wheelEvent(QWheelEvent *event); // non-property methods: void initializeParentPlot(QCustomPlot *parentPlot); void setParentLayerable(QCPLayerable *parentLayerable); bool moveToLayer(QCPLayer *layer, bool prepend); void applyAntialiasingHint(QCPPainter *painter, bool localAntialiased, QCP::AntialiasedElement overrideElement) const; private: Q_DISABLE_COPY(QCPLayerable) friend class QCustomPlot; friend class QCPLayer; friend class QCPAxisRect; }; /* end of 'src/layer.h' */ /* including file 'src/axis/range.h', size 5280 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPRange { public: double lower, upper; QCPRange(); QCPRange(double lower, double upper); bool operator==(const QCPRange &other) const { return lower == other.lower && upper == other.upper; } bool operator!=(const QCPRange &other) const { return !(*this == other); } QCPRange &operator+=(const double &value) { lower += value; upper += value; return *this; } QCPRange &operator-=(const double &value) { lower -= value; upper -= value; return *this; } QCPRange &operator*=(const double &value) { lower *= value; upper *= value; return *this; } QCPRange &operator/=(const double &value) { lower /= value; upper /= value; return *this; } friend inline const QCPRange operator+(const QCPRange &, double); friend inline const QCPRange operator+(double, const QCPRange &); friend inline const QCPRange operator-(const QCPRange &range, double value); friend inline const QCPRange operator*(const QCPRange &range, double value); friend inline const QCPRange operator*(double value, const QCPRange &range); friend inline const QCPRange operator/(const QCPRange &range, double value); double size() const { return upper - lower; } double center() const { return (upper + lower) * 0.5; } void normalize() { if (lower > upper) qSwap(lower, upper); } void expand(const QCPRange &otherRange); void expand(double includeCoord); QCPRange expanded(const QCPRange &otherRange) const; QCPRange expanded(double includeCoord) const; QCPRange bounded(double lowerBound, double upperBound) const; QCPRange sanitizedForLogScale() const; QCPRange sanitizedForLinScale() const; bool contains(double value) const { return value >= lower && value <= upper; } static bool validRange(double lower, double upper); static bool validRange(const QCPRange &range); static const double minRange; static const double maxRange; }; Q_DECLARE_TYPEINFO(QCPRange, Q_MOVABLE_TYPE); /*! \relates QCPRange Prints \a range in a human readable format to the qDebug output. */ inline QDebug operator<<(QDebug d, const QCPRange &range) { d.nospace() << "QCPRange(" << range.lower << ", " << range.upper << ")"; return d.space(); } /*! Adds \a value to both boundaries of the range. */ inline const QCPRange operator+(const QCPRange &range, double value) { QCPRange result(range); result += value; return result; } /*! Adds \a value to both boundaries of the range. */ inline const QCPRange operator+(double value, const QCPRange &range) { QCPRange result(range); result += value; return result; } /*! Subtracts \a value from both boundaries of the range. */ inline const QCPRange operator-(const QCPRange &range, double value) { QCPRange result(range); result -= value; return result; } /*! Multiplies both boundaries of the range by \a value. */ inline const QCPRange operator*(const QCPRange &range, double value) { QCPRange result(range); result *= value; return result; } /*! Multiplies both boundaries of the range by \a value. */ inline const QCPRange operator*(double value, const QCPRange &range) { QCPRange result(range); result *= value; return result; } /*! Divides both boundaries of the range by \a value. */ inline const QCPRange operator/(const QCPRange &range, double value) { QCPRange result(range); result /= value; return result; } /* end of 'src/axis/range.h' */ /* including file 'src/selection.h', size 8579 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPDataRange { public: QCPDataRange(); QCPDataRange(int begin, int end); bool operator==(const QCPDataRange &other) const { return mBegin == other.mBegin && mEnd == other.mEnd; } bool operator!=(const QCPDataRange &other) const { return !(*this == other); } // getters: int begin() const { return mBegin; } int end() const { return mEnd; } int size() const { return mEnd - mBegin; } int length() const { return size(); } // setters: void setBegin(int begin) { mBegin = begin; } void setEnd(int end) { mEnd = end; } // non-property methods: bool isValid() const { return (mEnd >= mBegin) && (mBegin >= 0); } bool isEmpty() const { return length() == 0; } QCPDataRange bounded(const QCPDataRange &other) const; QCPDataRange expanded(const QCPDataRange &other) const; QCPDataRange intersection(const QCPDataRange &other) const; QCPDataRange adjusted(int changeBegin, int changeEnd) const { return QCPDataRange(mBegin + changeBegin, mEnd + changeEnd); } bool intersects(const QCPDataRange &other) const; bool contains(const QCPDataRange &other) const; private: // property members: int mBegin, mEnd; }; Q_DECLARE_TYPEINFO(QCPDataRange, Q_MOVABLE_TYPE); class QCP_LIB_DECL QCPDataSelection { public: explicit QCPDataSelection(); explicit QCPDataSelection(const QCPDataRange &range); bool operator==(const QCPDataSelection &other) const; bool operator!=(const QCPDataSelection &other) const { return !(*this == other); } QCPDataSelection &operator+=(const QCPDataSelection &other); QCPDataSelection &operator+=(const QCPDataRange &other); QCPDataSelection &operator-=(const QCPDataSelection &other); QCPDataSelection &operator-=(const QCPDataRange &other); friend inline const QCPDataSelection operator+(const QCPDataSelection &a, const QCPDataSelection &b); friend inline const QCPDataSelection operator+(const QCPDataRange &a, const QCPDataSelection &b); friend inline const QCPDataSelection operator+(const QCPDataSelection &a, const QCPDataRange &b); friend inline const QCPDataSelection operator+(const QCPDataRange &a, const QCPDataRange &b); friend inline const QCPDataSelection operator-(const QCPDataSelection &a, const QCPDataSelection &b); friend inline const QCPDataSelection operator-(const QCPDataRange &a, const QCPDataSelection &b); friend inline const QCPDataSelection operator-(const QCPDataSelection &a, const QCPDataRange &b); friend inline const QCPDataSelection operator-(const QCPDataRange &a, const QCPDataRange &b); // getters: int dataRangeCount() const { return mDataRanges.size(); } int dataPointCount() const; QCPDataRange dataRange(int index = 0) const; QList dataRanges() const { return mDataRanges; } QCPDataRange span() const; // non-property methods: void addDataRange(const QCPDataRange &dataRange, bool simplify = true); void clear(); bool isEmpty() const { return mDataRanges.isEmpty(); } void simplify(); void enforceType(QCP::SelectionType type); bool contains(const QCPDataSelection &other) const; QCPDataSelection intersection(const QCPDataRange &other) const; QCPDataSelection intersection(const QCPDataSelection &other) const; QCPDataSelection inverse(const QCPDataRange &outerRange) const; private: // property members: QList mDataRanges; inline static bool lessThanDataRangeBegin(const QCPDataRange &a, const QCPDataRange &b) { return a.begin() < b.begin(); } }; Q_DECLARE_METATYPE(QCPDataSelection) /*! Return a \ref QCPDataSelection with the data points in \a a joined with the data points in \a b. The resulting data selection is already simplified (see \ref QCPDataSelection::simplify). */ inline const QCPDataSelection operator+(const QCPDataSelection &a, const QCPDataSelection &b) { QCPDataSelection result(a); result += b; return result; } /*! Return a \ref QCPDataSelection with the data points in \a a joined with the data points in \a b. The resulting data selection is already simplified (see \ref QCPDataSelection::simplify). */ inline const QCPDataSelection operator+(const QCPDataRange &a, const QCPDataSelection &b) { QCPDataSelection result(a); result += b; return result; } /*! Return a \ref QCPDataSelection with the data points in \a a joined with the data points in \a b. The resulting data selection is already simplified (see \ref QCPDataSelection::simplify). */ inline const QCPDataSelection operator+(const QCPDataSelection &a, const QCPDataRange &b) { QCPDataSelection result(a); result += b; return result; } /*! Return a \ref QCPDataSelection with the data points in \a a joined with the data points in \a b. The resulting data selection is already simplified (see \ref QCPDataSelection::simplify). */ inline const QCPDataSelection operator+(const QCPDataRange &a, const QCPDataRange &b) { QCPDataSelection result(a); result += b; return result; } /*! Return a \ref QCPDataSelection with the data points which are in \a a but not in \a b. */ inline const QCPDataSelection operator-(const QCPDataSelection &a, const QCPDataSelection &b) { QCPDataSelection result(a); result -= b; return result; } /*! Return a \ref QCPDataSelection with the data points which are in \a a but not in \a b. */ inline const QCPDataSelection operator-(const QCPDataRange &a, const QCPDataSelection &b) { QCPDataSelection result(a); result -= b; return result; } /*! Return a \ref QCPDataSelection with the data points which are in \a a but not in \a b. */ inline const QCPDataSelection operator-(const QCPDataSelection &a, const QCPDataRange &b) { QCPDataSelection result(a); result -= b; return result; } /*! Return a \ref QCPDataSelection with the data points which are in \a a but not in \a b. */ inline const QCPDataSelection operator-(const QCPDataRange &a, const QCPDataRange &b) { QCPDataSelection result(a); result -= b; return result; } /*! \relates QCPDataRange Prints \a dataRange in a human readable format to the qDebug output. */ inline QDebug operator<<(QDebug d, const QCPDataRange &dataRange) { d.nospace() << "[" << dataRange.begin() << ".." << dataRange.end() - 1 << "]"; return d.space(); } /*! \relates QCPDataSelection Prints \a selection in a human readable format to the qDebug output. */ inline QDebug operator<<(QDebug d, const QCPDataSelection &selection) { d.nospace() << "QCPDataSelection("; for (int i = 0; i < selection.dataRangeCount(); ++i) { if (i != 0) d << ", "; d << selection.dataRange(i); } d << ")"; return d.space(); } /* end of 'src/selection.h' */ /* including file 'src/selectionrect.h', size 3338 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPSelectionRect : public QCPLayerable { Q_OBJECT public: explicit QCPSelectionRect(QCustomPlot *parentPlot); ~QCPSelectionRect() override; // getters: QRect rect() const { return mRect; } QCPRange range(const QCPAxis *axis) const; QPen pen() const { return mPen; } QBrush brush() const { return mBrush; } bool isActive() const { return mActive; } // setters: void setPen(const QPen &pen); void setBrush(const QBrush &brush); // non-property methods: Q_SLOT void cancel(); signals: void started(QMouseEvent *event); void changed(const QRect &rect, QMouseEvent *event); void canceled(const QRect &rect, QInputEvent *event); void accepted(const QRect &rect, QMouseEvent *event); protected: // property members: QRect mRect; QPen mPen; QBrush mBrush; // non-property members: bool mActive; // introduced virtual methods: virtual void startSelection(QMouseEvent *event); virtual void moveSelection(QMouseEvent *event); virtual void endSelection(QMouseEvent *event); virtual void keyPressEvent(QKeyEvent *event); // reimplemented virtual methods void applyDefaultAntialiasingHint(QCPPainter *painter) const override; void draw(QCPPainter *painter) override; friend class QCustomPlot; }; /* end of 'src/selectionrect.h' */ /* including file 'src/layout.h', size 13128 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPMarginGroup : public QObject { Q_OBJECT public: explicit QCPMarginGroup(QCustomPlot *parentPlot); ~QCPMarginGroup() override; // non-virtual methods: QList elements(QCP::MarginSide side) const { return mChildren.value(side); } bool isEmpty() const; void clear(); protected: // non-property members: QCustomPlot *mParentPlot; QHash> mChildren; // introduced virtual methods: virtual int commonMargin(QCP::MarginSide side) const; // non-virtual methods: void addChild(QCP::MarginSide side, QCPLayoutElement *element); void removeChild(QCP::MarginSide side, QCPLayoutElement *element); private: Q_DISABLE_COPY(QCPMarginGroup) friend class QCPLayoutElement; }; class QCP_LIB_DECL QCPLayoutElement : public QCPLayerable { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QCPLayout *layout READ layout) Q_PROPERTY(QRect rect READ rect) Q_PROPERTY(QRect outerRect READ outerRect WRITE setOuterRect) Q_PROPERTY(QMargins margins READ margins WRITE setMargins) Q_PROPERTY(QMargins minimumMargins READ minimumMargins WRITE setMinimumMargins) Q_PROPERTY(QSize minimumSize READ minimumSize WRITE setMinimumSize) Q_PROPERTY(QSize maximumSize READ maximumSize WRITE setMaximumSize) /// \endcond public: /*! Defines the phases of the update process, that happens just before a replot. At each phase, \ref update is called with the according UpdatePhase value. */ enum UpdatePhase { upPreparation ///< Phase used for any type of preparation that needs to be done before margin calculation and layout , upMargins ///< Phase in which the margins are calculated and set , upLayout ///< Final phase in which the layout system places the rects of the elements }; Q_ENUM(UpdatePhase) - explicit QCPLayoutElement(QCustomPlot *parentPlot = 0); + explicit QCPLayoutElement(QCustomPlot *parentPlot = nullptr); ~QCPLayoutElement() override; // getters: QCPLayout *layout() const { return mParentLayout; } QRect rect() const { return mRect; } QRect outerRect() const { return mOuterRect; } QMargins margins() const { return mMargins; } QMargins minimumMargins() const { return mMinimumMargins; } QCP::MarginSides autoMargins() const { return mAutoMargins; } QSize minimumSize() const { return mMinimumSize; } QSize maximumSize() const { return mMaximumSize; } - QCPMarginGroup *marginGroup(QCP::MarginSide side) const { return mMarginGroups.value(side, (QCPMarginGroup *)0); } + QCPMarginGroup *marginGroup(QCP::MarginSide side) const { return mMarginGroups.value(side, (QCPMarginGroup *)nullptr); } QHash marginGroups() const { return mMarginGroups; } // setters: void setOuterRect(const QRect &rect); void setMargins(const QMargins &margins); void setMinimumMargins(const QMargins &margins); void setAutoMargins(QCP::MarginSides sides); void setMinimumSize(const QSize &size); void setMinimumSize(int width, int height); void setMaximumSize(const QSize &size); void setMaximumSize(int width, int height); void setMarginGroup(QCP::MarginSides sides, QCPMarginGroup *group); // introduced virtual methods: virtual void update(UpdatePhase phase); virtual QSize minimumSizeHint() const; virtual QSize maximumSizeHint() const; virtual QList elements(bool recursive) const; // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; protected: // property members: QCPLayout *mParentLayout; QSize mMinimumSize, mMaximumSize; QRect mRect, mOuterRect; QMargins mMargins, mMinimumMargins; QCP::MarginSides mAutoMargins; QHash mMarginGroups; // introduced virtual methods: virtual int calculateAutoMargin(QCP::MarginSide side); virtual void layoutChanged(); // reimplemented virtual methods: void applyDefaultAntialiasingHint(QCPPainter *painter) const override { Q_UNUSED(painter) } void draw(QCPPainter *painter) override { Q_UNUSED(painter) } void parentPlotInitialized(QCustomPlot *parentPlot) override; private: Q_DISABLE_COPY(QCPLayoutElement) friend class QCustomPlot; friend class QCPLayout; friend class QCPMarginGroup; }; Q_DECLARE_METATYPE(QCPLayoutElement::UpdatePhase) class QCP_LIB_DECL QCPLayout : public QCPLayoutElement { Q_OBJECT public: explicit QCPLayout(); // reimplemented virtual methods: void update(UpdatePhase phase) override; QList elements(bool recursive) const override; // introduced virtual methods: virtual int elementCount() const = 0; virtual QCPLayoutElement *elementAt(int index) const = 0; virtual QCPLayoutElement *takeAt(int index) = 0; virtual bool take(QCPLayoutElement *element) = 0; virtual void simplify(); // non-virtual methods: bool removeAt(int index); bool remove(QCPLayoutElement *element); void clear(); protected: // introduced virtual methods: virtual void updateLayout(); // non-virtual methods: void sizeConstraintsChanged() const; void adoptElement(QCPLayoutElement *el); void releaseElement(QCPLayoutElement *el); QVector getSectionSizes(QVector maxSizes, QVector minSizes, QVector stretchFactors, int totalSize) const; private: Q_DISABLE_COPY(QCPLayout) friend class QCPLayoutElement; }; class QCP_LIB_DECL QCPLayoutGrid : public QCPLayout { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(int rowCount READ rowCount) Q_PROPERTY(int columnCount READ columnCount) Q_PROPERTY(QList columnStretchFactors READ columnStretchFactors WRITE setColumnStretchFactors) Q_PROPERTY(QList rowStretchFactors READ rowStretchFactors WRITE setRowStretchFactors) Q_PROPERTY(int columnSpacing READ columnSpacing WRITE setColumnSpacing) Q_PROPERTY(int rowSpacing READ rowSpacing WRITE setRowSpacing) Q_PROPERTY(FillOrder fillOrder READ fillOrder WRITE setFillOrder) Q_PROPERTY(int wrap READ wrap WRITE setWrap) /// \endcond public: /*! Defines in which direction the grid is filled when using \ref addElement(QCPLayoutElement*). The column/row at which wrapping into the next row/column occurs can be specified with \ref setWrap. \see setFillOrder */ enum FillOrder { foRowsFirst ///< Rows are filled first, and a new element is wrapped to the next column if the row count would exceed \ref setWrap. , foColumnsFirst ///< Columns are filled first, and a new element is wrapped to the next row if the column count would exceed \ref setWrap. }; Q_ENUM(FillOrder) explicit QCPLayoutGrid(); ~QCPLayoutGrid() override; // getters: int rowCount() const { return mElements.size(); } int columnCount() const { return mElements.size() > 0 ? mElements.first().size() : 0; } QList columnStretchFactors() const { return mColumnStretchFactors; } QList rowStretchFactors() const { return mRowStretchFactors; } int columnSpacing() const { return mColumnSpacing; } int rowSpacing() const { return mRowSpacing; } int wrap() const { return mWrap; } FillOrder fillOrder() const { return mFillOrder; } // setters: void setColumnStretchFactor(int column, double factor); void setColumnStretchFactors(const QList &factors); void setRowStretchFactor(int row, double factor); void setRowStretchFactors(const QList &factors); void setColumnSpacing(int pixels); void setRowSpacing(int pixels); void setWrap(int count); void setFillOrder(FillOrder order, bool rearrange = true); // reimplemented virtual methods: void updateLayout() override; int elementCount() const override { return rowCount() * columnCount(); } QCPLayoutElement *elementAt(int index) const override; QCPLayoutElement *takeAt(int index) override; bool take(QCPLayoutElement *element) override; QList elements(bool recursive) const override; void simplify() override; QSize minimumSizeHint() const override; QSize maximumSizeHint() const override; // non-virtual methods: QCPLayoutElement *element(int row, int column) const; bool addElement(int row, int column, QCPLayoutElement *element); bool addElement(QCPLayoutElement *element); bool hasElement(int row, int column); void expandTo(int newRowCount, int newColumnCount); void insertRow(int newIndex); void insertColumn(int newIndex); int rowColToIndex(int row, int column) const; void indexToRowCol(int index, int &row, int &column) const; protected: // property members: QList> mElements; QList mColumnStretchFactors; QList mRowStretchFactors; int mColumnSpacing, mRowSpacing; int mWrap; FillOrder mFillOrder; // non-virtual methods: void getMinimumRowColSizes(QVector *minColWidths, QVector *minRowHeights) const; void getMaximumRowColSizes(QVector *maxColWidths, QVector *maxRowHeights) const; private: Q_DISABLE_COPY(QCPLayoutGrid) }; Q_DECLARE_METATYPE(QCPLayoutGrid::FillOrder) class QCP_LIB_DECL QCPLayoutInset : public QCPLayout { Q_OBJECT public: /*! Defines how the placement and sizing is handled for a certain element in a QCPLayoutInset. */ enum InsetPlacement { ipFree ///< The element may be positioned/sized arbitrarily, see \ref setInsetRect , ipBorderAligned ///< The element is aligned to one of the layout sides, see \ref setInsetAlignment }; Q_ENUM(InsetPlacement) explicit QCPLayoutInset(); ~QCPLayoutInset() override; // getters: InsetPlacement insetPlacement(int index) const; Qt::Alignment insetAlignment(int index) const; QRectF insetRect(int index) const; // setters: void setInsetPlacement(int index, InsetPlacement placement); void setInsetAlignment(int index, Qt::Alignment alignment); void setInsetRect(int index, const QRectF &rect); // reimplemented virtual methods: void updateLayout() override; int elementCount() const override; QCPLayoutElement *elementAt(int index) const override; QCPLayoutElement *takeAt(int index) override; bool take(QCPLayoutElement *element) override; void simplify() override {} - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; // non-virtual methods: void addElement(QCPLayoutElement *element, Qt::Alignment alignment); void addElement(QCPLayoutElement *element, const QRectF &rect); protected: // property members: QList mElements; QList mInsetPlacement; QList mInsetAlignment; QList mInsetRect; private: Q_DISABLE_COPY(QCPLayoutInset) }; Q_DECLARE_METATYPE(QCPLayoutInset::InsetPlacement) /* end of 'src/layout.h' */ /* including file 'src/lineending.h', size 4426 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPLineEnding { Q_GADGET public: /*! Defines the type of ending decoration for line-like items, e.g. an arrow. \image html QCPLineEnding.png The width and length of these decorations can be controlled with the functions \ref setWidth and \ref setLength. Some decorations like \ref esDisc, \ref esSquare, \ref esDiamond and \ref esBar only support a width, the length property is ignored. \see QCPItemLine::setHead, QCPItemLine::setTail, QCPItemCurve::setHead, QCPItemCurve::setTail, QCPAxis::setLowerEnding, QCPAxis::setUpperEnding */ enum EndingStyle { esNone ///< No ending decoration , esFlatArrow ///< A filled arrow head with a straight/flat back (a triangle) , esSpikeArrow ///< A filled arrow head with an indented back , esLineArrow ///< A non-filled arrow head with open back , esDisc ///< A filled circle , esSquare ///< A filled square , esDiamond ///< A filled diamond (45 degrees rotated square) , esBar ///< A bar perpendicular to the line , esHalfBar ///< A bar perpendicular to the line, pointing out to only one side (to which side can be changed with \ref setInverted) , esSkewedBar ///< A bar that is skewed (skew controllable via \ref setLength) }; Q_ENUM(EndingStyle) QCPLineEnding(); QCPLineEnding(EndingStyle style, double width = 8, double length = 10, bool inverted = false); // getters: EndingStyle style() const { return mStyle; } double width() const { return mWidth; } double length() const { return mLength; } bool inverted() const { return mInverted; } // setters: void setStyle(EndingStyle style); void setWidth(double width); void setLength(double length); void setInverted(bool inverted); // non-property methods: double boundingDistance() const; double realLength() const; void draw(QCPPainter *painter, const QCPVector2D &pos, const QCPVector2D &dir) const; void draw(QCPPainter *painter, const QCPVector2D &pos, double angle) const; protected: // property members: EndingStyle mStyle; double mWidth, mLength; bool mInverted; }; Q_DECLARE_TYPEINFO(QCPLineEnding, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(QCPLineEnding::EndingStyle) /* end of 'src/lineending.h' */ /* including file 'src/axis/axisticker.h', size 4177 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPAxisTicker { Q_GADGET public: /*! Defines the strategies that the axis ticker may follow when choosing the size of the tick step. \see setTickStepStrategy */ enum TickStepStrategy { tssReadability ///< A nicely readable tick step is prioritized over matching the requested number of ticks (see \ref setTickCount) , tssMeetTickCount ///< Less readable tick steps are allowed which in turn facilitates getting closer to the requested tick count }; Q_ENUM(TickStepStrategy) QCPAxisTicker(); virtual ~QCPAxisTicker(); // getters: TickStepStrategy tickStepStrategy() const { return mTickStepStrategy; } int tickCount() const { return mTickCount; } double tickOrigin() const { return mTickOrigin; } // setters: void setTickStepStrategy(TickStepStrategy strategy); void setTickCount(int count); void setTickOrigin(double origin); // introduced virtual methods: virtual void generate(const QCPRange &range, const QLocale &locale, QChar formatChar, int precision, QVector &ticks, QVector *subTicks, QVector *tickLabels); protected: // property members: TickStepStrategy mTickStepStrategy; int mTickCount; double mTickOrigin; // introduced virtual methods: virtual double getTickStep(const QCPRange &range); virtual int getSubTickCount(double tickStep); virtual QString getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision); virtual QVector createTickVector(double tickStep, const QCPRange &range); virtual QVector createSubTickVector(int subTickCount, const QVector &ticks); virtual QVector createLabelVector(const QVector &ticks, const QLocale &locale, QChar formatChar, int precision); // non-virtual methods: void trimTicks(const QCPRange &range, QVector &ticks, bool keepOneOutlier) const; double pickClosest(double target, const QVector &candidates) const; - double getMantissa(double input, double *magnitude = 0) const; + double getMantissa(double input, double *magnitude = nullptr) const; double cleanMantissa(double input) const; }; Q_DECLARE_METATYPE(QCPAxisTicker::TickStepStrategy) Q_DECLARE_METATYPE(QSharedPointer) /* end of 'src/axis/axisticker.h' */ /* including file 'src/axis/axistickerdatetime.h', size 3289 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPAxisTickerDateTime : public QCPAxisTicker { public: QCPAxisTickerDateTime(); // getters: QString dateTimeFormat() const { return mDateTimeFormat; } Qt::TimeSpec dateTimeSpec() const { return mDateTimeSpec; } // setters: void setDateTimeFormat(const QString &format); void setDateTimeSpec(Qt::TimeSpec spec); void setTickOrigin( double origin); // hides base class method but calls baseclass implementation ("using" throws off IDEs and doxygen) void setTickOrigin(const QDateTime &origin); // static methods: static QDateTime keyToDateTime(double key); static double dateTimeToKey(const QDateTime dateTime); static double dateTimeToKey(const QDate date); protected: // property members: QString mDateTimeFormat; Qt::TimeSpec mDateTimeSpec; // non-property members: enum DateStrategy { dsNone, dsUniformTimeInDay, dsUniformDayInMonth } mDateStrategy; // reimplemented virtual methods: double getTickStep(const QCPRange &range) override; int getSubTickCount(double tickStep) override; QString getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision) override; QVector createTickVector(double tickStep, const QCPRange &range) override; }; /* end of 'src/axis/axistickerdatetime.h' */ /* including file 'src/axis/axistickertime.h', size 3288 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPAxisTickerTime : public QCPAxisTicker { Q_GADGET public: /*! Defines the logical units in which fractions of time spans can be expressed. \see setFieldWidth, setTimeFormat */ enum TimeUnit { tuMilliseconds, tuSeconds, tuMinutes, tuHours, tuDays }; Q_ENUM(TimeUnit) QCPAxisTickerTime(); // getters: QString timeFormat() const { return mTimeFormat; } int fieldWidth(TimeUnit unit) const { return mFieldWidth.value(unit); } // setters: void setTimeFormat(const QString &format); void setFieldWidth(TimeUnit unit, int width); protected: // property members: QString mTimeFormat; QHash mFieldWidth; // non-property members: TimeUnit mSmallestUnit, mBiggestUnit; QHash mFormatPattern; // reimplemented virtual methods: double getTickStep(const QCPRange &range) override; int getSubTickCount(double tickStep) override; QString getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision) override; // non-virtual methods: void replaceUnit(QString &text, TimeUnit unit, int value) const; }; Q_DECLARE_METATYPE(QCPAxisTickerTime::TimeUnit) /* end of 'src/axis/axistickertime.h' */ /* including file 'src/axis/axistickerfixed.h', size 3308 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPAxisTickerFixed : public QCPAxisTicker { Q_GADGET public: /*! Defines how the axis ticker may modify the specified tick step (\ref setTickStep) in order to control the number of ticks in the axis range. \see setScaleStrategy */ enum ScaleStrategy { ssNone ///< Modifications are not allowed, the specified tick step is absolutely fixed. This might cause a high tick density and overlapping labels if the axis range is zoomed out. , ssMultiples ///< An integer multiple of the specified tick step is allowed. The used factor follows the base class properties of \ref setTickStepStrategy and \ref setTickCount. , ssPowers ///< An integer power of the specified tick step is allowed. }; Q_ENUM(ScaleStrategy) QCPAxisTickerFixed(); // getters: double tickStep() const { return mTickStep; } ScaleStrategy scaleStrategy() const { return mScaleStrategy; } // setters: void setTickStep(double step); void setScaleStrategy(ScaleStrategy strategy); protected: // property members: double mTickStep; ScaleStrategy mScaleStrategy; // reimplemented virtual methods: double getTickStep(const QCPRange &range) override; }; Q_DECLARE_METATYPE(QCPAxisTickerFixed::ScaleStrategy) /* end of 'src/axis/axistickerfixed.h' */ /* including file 'src/axis/axistickertext.h', size 3085 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPAxisTickerText : public QCPAxisTicker { public: QCPAxisTickerText(); // getters: QMap &ticks() { return mTicks; } int subTickCount() const { return mSubTickCount; } // setters: void setTicks(const QMap &ticks); void setTicks(const QVector &positions, const QVector labels); void setSubTickCount(int subTicks); // non-virtual methods: void clear(); void addTick(double position, QString label); void addTicks(const QMap &ticks); void addTicks(const QVector &positions, const QVector &labels); protected: // property members: QMap mTicks; int mSubTickCount; // reimplemented virtual methods: double getTickStep(const QCPRange &range) override; int getSubTickCount(double tickStep) override; QString getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision) override; QVector createTickVector(double tickStep, const QCPRange &range) override; }; /* end of 'src/axis/axistickertext.h' */ /* including file 'src/axis/axistickerpi.h', size 3911 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPAxisTickerPi : public QCPAxisTicker { Q_GADGET public: /*! Defines how fractions should be displayed in tick labels. \see setFractionStyle */ enum FractionStyle { fsFloatingPoint ///< Fractions are displayed as regular decimal floating point numbers, e.g. "0.25" or "0.125". , fsAsciiFractions ///< Fractions are written as rationals using ASCII characters only, e.g. "1/4" or "1/8" , fsUnicodeFractions ///< Fractions are written using sub- and superscript UTF-8 digits and the fraction symbol. }; Q_ENUM(FractionStyle) QCPAxisTickerPi(); // getters: QString piSymbol() const { return mPiSymbol; } double piValue() const { return mPiValue; } bool periodicity() const { return mPeriodicity; } FractionStyle fractionStyle() const { return mFractionStyle; } // setters: void setPiSymbol(QString symbol); void setPiValue(double pi); void setPeriodicity(int multiplesOfPi); void setFractionStyle(FractionStyle style); protected: // property members: QString mPiSymbol; double mPiValue; int mPeriodicity; FractionStyle mFractionStyle; // non-property members: double mPiTickStep; // size of one tick step in units of mPiValue // reimplemented virtual methods: double getTickStep(const QCPRange &range) override; int getSubTickCount(double tickStep) override; QString getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision) override; // non-virtual methods: void simplifyFraction(int &numerator, int &denominator) const; QString fractionToString(int numerator, int denominator) const; QString unicodeFraction(int numerator, int denominator) const; QString unicodeSuperscript(int number) const; QString unicodeSubscript(int number) const; }; Q_DECLARE_METATYPE(QCPAxisTickerPi::FractionStyle) /* end of 'src/axis/axistickerpi.h' */ /* including file 'src/axis/axistickerlog.h', size 2663 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPAxisTickerLog : public QCPAxisTicker { public: QCPAxisTickerLog(); // getters: double logBase() const { return mLogBase; } int subTickCount() const { return mSubTickCount; } // setters: void setLogBase(double base); void setSubTickCount(int subTicks); protected: // property members: double mLogBase; int mSubTickCount; // non-property members: double mLogBaseLnInv; // reimplemented virtual methods: double getTickStep(const QCPRange &range) override; int getSubTickCount(double tickStep) override; QVector createTickVector(double tickStep, const QCPRange &range) override; }; /* end of 'src/axis/axistickerlog.h' */ /* including file 'src/axis/axis.h', size 20230 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPGrid : public QCPLayerable { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(bool subGridVisible READ subGridVisible WRITE setSubGridVisible) Q_PROPERTY(bool antialiasedSubGrid READ antialiasedSubGrid WRITE setAntialiasedSubGrid) Q_PROPERTY(bool antialiasedZeroLine READ antialiasedZeroLine WRITE setAntialiasedZeroLine) Q_PROPERTY(QPen pen READ pen WRITE setPen) Q_PROPERTY(QPen subGridPen READ subGridPen WRITE setSubGridPen) Q_PROPERTY(QPen zeroLinePen READ zeroLinePen WRITE setZeroLinePen) /// \endcond public: explicit QCPGrid(QCPAxis *parentAxis); // getters: bool subGridVisible() const { return mSubGridVisible; } bool antialiasedSubGrid() const { return mAntialiasedSubGrid; } bool antialiasedZeroLine() const { return mAntialiasedZeroLine; } QPen pen() const { return mPen; } QPen subGridPen() const { return mSubGridPen; } QPen zeroLinePen() const { return mZeroLinePen; } // setters: void setSubGridVisible(bool visible); void setAntialiasedSubGrid(bool enabled); void setAntialiasedZeroLine(bool enabled); void setPen(const QPen &pen); void setSubGridPen(const QPen &pen); void setZeroLinePen(const QPen &pen); protected: // property members: bool mSubGridVisible; bool mAntialiasedSubGrid, mAntialiasedZeroLine; QPen mPen, mSubGridPen, mZeroLinePen; // non-property members: QCPAxis *mParentAxis; // reimplemented virtual methods: void applyDefaultAntialiasingHint(QCPPainter *painter) const override; void draw(QCPPainter *painter) override; // non-virtual methods: void drawGridLines(QCPPainter *painter) const; void drawSubGridLines(QCPPainter *painter) const; friend class QCPAxis; }; class QCP_LIB_DECL QCPAxis : public QCPLayerable { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(AxisType axisType READ axisType) Q_PROPERTY(QCPAxisRect *axisRect READ axisRect) Q_PROPERTY(ScaleType scaleType READ scaleType WRITE setScaleType NOTIFY scaleTypeChanged) Q_PROPERTY(QCPRange range READ range WRITE setRange NOTIFY rangeChanged) Q_PROPERTY(bool rangeReversed READ rangeReversed WRITE setRangeReversed) Q_PROPERTY(QSharedPointer ticker READ ticker WRITE setTicker) Q_PROPERTY(bool ticks READ ticks WRITE setTicks) Q_PROPERTY(bool tickLabels READ tickLabels WRITE setTickLabels) Q_PROPERTY(int tickLabelPadding READ tickLabelPadding WRITE setTickLabelPadding) Q_PROPERTY(QFont tickLabelFont READ tickLabelFont WRITE setTickLabelFont) Q_PROPERTY(QColor tickLabelColor READ tickLabelColor WRITE setTickLabelColor) Q_PROPERTY(double tickLabelRotation READ tickLabelRotation WRITE setTickLabelRotation) Q_PROPERTY(LabelSide tickLabelSide READ tickLabelSide WRITE setTickLabelSide) Q_PROPERTY(QString numberFormat READ numberFormat WRITE setNumberFormat) Q_PROPERTY(int numberPrecision READ numberPrecision WRITE setNumberPrecision) Q_PROPERTY(QVector tickVector READ tickVector) Q_PROPERTY(QVector tickVectorLabels READ tickVectorLabels) Q_PROPERTY(int tickLengthIn READ tickLengthIn WRITE setTickLengthIn) Q_PROPERTY(int tickLengthOut READ tickLengthOut WRITE setTickLengthOut) Q_PROPERTY(bool subTicks READ subTicks WRITE setSubTicks) Q_PROPERTY(int subTickLengthIn READ subTickLengthIn WRITE setSubTickLengthIn) Q_PROPERTY(int subTickLengthOut READ subTickLengthOut WRITE setSubTickLengthOut) Q_PROPERTY(QPen basePen READ basePen WRITE setBasePen) Q_PROPERTY(QPen tickPen READ tickPen WRITE setTickPen) Q_PROPERTY(QPen subTickPen READ subTickPen WRITE setSubTickPen) Q_PROPERTY(QFont labelFont READ labelFont WRITE setLabelFont) Q_PROPERTY(QColor labelColor READ labelColor WRITE setLabelColor) Q_PROPERTY(QString label READ label WRITE setLabel) Q_PROPERTY(int labelPadding READ labelPadding WRITE setLabelPadding) Q_PROPERTY(int padding READ padding WRITE setPadding) Q_PROPERTY(int offset READ offset WRITE setOffset) Q_PROPERTY(SelectableParts selectedParts READ selectedParts WRITE setSelectedParts NOTIFY selectionChanged) Q_PROPERTY(SelectableParts selectableParts READ selectableParts WRITE setSelectableParts NOTIFY selectableChanged) Q_PROPERTY(QFont selectedTickLabelFont READ selectedTickLabelFont WRITE setSelectedTickLabelFont) Q_PROPERTY(QFont selectedLabelFont READ selectedLabelFont WRITE setSelectedLabelFont) Q_PROPERTY(QColor selectedTickLabelColor READ selectedTickLabelColor WRITE setSelectedTickLabelColor) Q_PROPERTY(QColor selectedLabelColor READ selectedLabelColor WRITE setSelectedLabelColor) Q_PROPERTY(QPen selectedBasePen READ selectedBasePen WRITE setSelectedBasePen) Q_PROPERTY(QPen selectedTickPen READ selectedTickPen WRITE setSelectedTickPen) Q_PROPERTY(QPen selectedSubTickPen READ selectedSubTickPen WRITE setSelectedSubTickPen) Q_PROPERTY(QCPLineEnding lowerEnding READ lowerEnding WRITE setLowerEnding) Q_PROPERTY(QCPLineEnding upperEnding READ upperEnding WRITE setUpperEnding) Q_PROPERTY(QCPGrid *grid READ grid) /// \endcond public: /*! Defines at which side of the axis rect the axis will appear. This also affects how the tick marks are drawn, on which side the labels are placed etc. */ enum AxisType { atLeft = 0x01 ///< 0x01 Axis is vertical and on the left side of the axis rect , atRight = 0x02 ///< 0x02 Axis is vertical and on the right side of the axis rect , atTop = 0x04 ///< 0x04 Axis is horizontal and on the top side of the axis rect , atBottom = 0x08 ///< 0x08 Axis is horizontal and on the bottom side of the axis rect }; Q_ENUM(AxisType) Q_FLAGS(AxisTypes) Q_DECLARE_FLAGS(AxisTypes, AxisType) /*! Defines on which side of the axis the tick labels (numbers) shall appear. \see setTickLabelSide */ enum LabelSide { lsInside ///< Tick labels will be displayed inside the axis rect and clipped to the inner axis rect , lsOutside ///< Tick labels will be displayed outside the axis rect }; Q_ENUM(LabelSide) /*! Defines the scale of an axis. \see setScaleType */ enum ScaleType { stLinear ///< Linear scaling , stLogarithmic ///< Logarithmic scaling with correspondingly transformed axis coordinates (possibly also \ref setTicker to a \ref QCPAxisTickerLog instance). }; Q_ENUM(ScaleType) /*! Defines the selectable parts of an axis. \see setSelectableParts, setSelectedParts */ enum SelectablePart { spNone = 0 ///< None of the selectable parts , spAxis = 0x001 ///< The axis backbone and tick marks , spTickLabels = 0x002 ///< Tick labels (numbers) of this axis (as a whole, not individually) , spAxisLabel = 0x004 ///< The axis label }; Q_ENUM(SelectablePart) Q_FLAGS(SelectableParts) Q_DECLARE_FLAGS(SelectableParts, SelectablePart) explicit QCPAxis(QCPAxisRect *parent, AxisType type); ~QCPAxis() override; // getters: AxisType axisType() const { return mAxisType; } QCPAxisRect *axisRect() const { return mAxisRect; } ScaleType scaleType() const { return mScaleType; } const QCPRange range() const { return mRange; } bool rangeReversed() const { return mRangeReversed; } QSharedPointer ticker() const { return mTicker; } bool ticks() const { return mTicks; } bool tickLabels() const { return mTickLabels; } int tickLabelPadding() const; QFont tickLabelFont() const { return mTickLabelFont; } QColor tickLabelColor() const { return mTickLabelColor; } double tickLabelRotation() const; LabelSide tickLabelSide() const; QString numberFormat() const; int numberPrecision() const { return mNumberPrecision; } QVector tickVector() const { return mTickVector; } QVector tickVectorLabels() const { return mTickVectorLabels; } int tickLengthIn() const; int tickLengthOut() const; bool subTicks() const { return mSubTicks; } int subTickLengthIn() const; int subTickLengthOut() const; QPen basePen() const { return mBasePen; } QPen tickPen() const { return mTickPen; } QPen subTickPen() const { return mSubTickPen; } QFont labelFont() const { return mLabelFont; } QColor labelColor() const { return mLabelColor; } QString label() const { return mLabel; } int labelPadding() const; int padding() const { return mPadding; } int offset() const; SelectableParts selectedParts() const { return mSelectedParts; } SelectableParts selectableParts() const { return mSelectableParts; } QFont selectedTickLabelFont() const { return mSelectedTickLabelFont; } QFont selectedLabelFont() const { return mSelectedLabelFont; } QColor selectedTickLabelColor() const { return mSelectedTickLabelColor; } QColor selectedLabelColor() const { return mSelectedLabelColor; } QPen selectedBasePen() const { return mSelectedBasePen; } QPen selectedTickPen() const { return mSelectedTickPen; } QPen selectedSubTickPen() const { return mSelectedSubTickPen; } QCPLineEnding lowerEnding() const; QCPLineEnding upperEnding() const; QCPGrid *grid() const { return mGrid; } // setters: Q_SLOT void setScaleType(QCPAxis::ScaleType type); Q_SLOT void setRange(const QCPRange &range); void setRange(double lower, double upper); void setRange(double position, double size, Qt::AlignmentFlag alignment); void setRangeLower(double lower); void setRangeUpper(double upper); void setRangeReversed(bool reversed); void setTicker(QSharedPointer ticker); void setTicks(bool show); void setTickLabels(bool show); void setTickLabelPadding(int padding); void setTickLabelFont(const QFont &font); void setTickLabelColor(const QColor &color); void setTickLabelRotation(double degrees); void setTickLabelSide(LabelSide side); void setNumberFormat(const QString &formatCode); void setNumberPrecision(int precision); void setTickLength(int inside, int outside = 0); void setTickLengthIn(int inside); void setTickLengthOut(int outside); void setSubTicks(bool show); void setSubTickLength(int inside, int outside = 0); void setSubTickLengthIn(int inside); void setSubTickLengthOut(int outside); void setBasePen(const QPen &pen); void setTickPen(const QPen &pen); void setSubTickPen(const QPen &pen); void setLabelFont(const QFont &font); void setLabelColor(const QColor &color); void setLabel(const QString &str); void setLabelPadding(int padding); void setPadding(int padding); void setOffset(int offset); void setSelectedTickLabelFont(const QFont &font); void setSelectedLabelFont(const QFont &font); void setSelectedTickLabelColor(const QColor &color); void setSelectedLabelColor(const QColor &color); void setSelectedBasePen(const QPen &pen); void setSelectedTickPen(const QPen &pen); void setSelectedSubTickPen(const QPen &pen); Q_SLOT void setSelectableParts(const QCPAxis::SelectableParts &selectableParts); Q_SLOT void setSelectedParts(const QCPAxis::SelectableParts &selectedParts); void setLowerEnding(const QCPLineEnding &ending); void setUpperEnding(const QCPLineEnding &ending); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; // non-property methods: Qt::Orientation orientation() const { return mOrientation; } int pixelOrientation() const { return rangeReversed() != (orientation() == Qt::Vertical) ? -1 : 1; } void moveRange(double diff); void scaleRange(double factor); void scaleRange(double factor, double center); void setScaleRatio(const QCPAxis *otherAxis, double ratio = 1.0); void rescale(bool onlyVisiblePlottables = false); double pixelToCoord(double value) const; double coordToPixel(double value) const; SelectablePart getPartAt(const QPointF &pos) const; QList plottables() const; QList graphs() const; QList items() const; static AxisType marginSideToAxisType(QCP::MarginSide side); static Qt::Orientation orientation(AxisType type) { return type == atBottom || type == atTop ? Qt::Horizontal : Qt::Vertical; } static AxisType opposite(AxisType type); signals: void rangeChanged(const QCPRange &newRange); void rangeChanged(const QCPRange &newRange, const QCPRange &oldRange); void scaleTypeChanged(QCPAxis::ScaleType scaleType); void selectionChanged(const QCPAxis::SelectableParts &parts); void selectableChanged(const QCPAxis::SelectableParts &parts); protected: // property members: // axis base: AxisType mAxisType; QCPAxisRect *mAxisRect; //int mOffset; // in QCPAxisPainter int mPadding; Qt::Orientation mOrientation; SelectableParts mSelectableParts, mSelectedParts; QPen mBasePen, mSelectedBasePen; //QCPLineEnding mLowerEnding, mUpperEnding; // in QCPAxisPainter // axis label: //int mLabelPadding; // in QCPAxisPainter QString mLabel; QFont mLabelFont, mSelectedLabelFont; QColor mLabelColor, mSelectedLabelColor; // tick labels: //int mTickLabelPadding; // in QCPAxisPainter bool mTickLabels; //double mTickLabelRotation; // in QCPAxisPainter QFont mTickLabelFont, mSelectedTickLabelFont; QColor mTickLabelColor, mSelectedTickLabelColor; int mNumberPrecision; QLatin1Char mNumberFormatChar; bool mNumberBeautifulPowers; //bool mNumberMultiplyCross; // QCPAxisPainter // ticks and subticks: bool mTicks; bool mSubTicks; //int mTickLengthIn, mTickLengthOut, mSubTickLengthIn, mSubTickLengthOut; // QCPAxisPainter QPen mTickPen, mSelectedTickPen; QPen mSubTickPen, mSelectedSubTickPen; // scale and range: QCPRange mRange; bool mRangeReversed; ScaleType mScaleType; // non-property members: QCPGrid *mGrid; QCPAxisPainterPrivate *mAxisPainter; QSharedPointer mTicker; QVector mTickVector; QVector mTickVectorLabels; QVector mSubTickVector; bool mCachedMarginValid; int mCachedMargin; // introduced virtual methods: virtual int calculateMargin(); // reimplemented virtual methods: void applyDefaultAntialiasingHint(QCPPainter *painter) const override; void draw(QCPPainter *painter) override; QCP::Interaction selectionCategory() const override; // events: void selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) override; void deselectEvent(bool *selectionStateChanged) override; // non-virtual methods: void setupTickVectors(); QPen getBasePen() const; QPen getTickPen() const; QPen getSubTickPen() const; QFont getTickLabelFont() const; QFont getLabelFont() const; QColor getTickLabelColor() const; QColor getLabelColor() const; private: Q_DISABLE_COPY(QCPAxis) friend class QCustomPlot; friend class QCPGrid; friend class QCPAxisRect; }; Q_DECLARE_OPERATORS_FOR_FLAGS(QCPAxis::SelectableParts) Q_DECLARE_OPERATORS_FOR_FLAGS(QCPAxis::AxisTypes) Q_DECLARE_METATYPE(QCPAxis::AxisType) Q_DECLARE_METATYPE(QCPAxis::LabelSide) Q_DECLARE_METATYPE(QCPAxis::ScaleType) Q_DECLARE_METATYPE(QCPAxis::SelectablePart) class QCPAxisPainterPrivate { public: explicit QCPAxisPainterPrivate(QCustomPlot *parentPlot); virtual ~QCPAxisPainterPrivate(); virtual void draw(QCPPainter *painter); virtual int size() const; void clearCache(); QRect axisSelectionBox() const { return mAxisSelectionBox; } QRect tickLabelsSelectionBox() const { return mTickLabelsSelectionBox; } QRect labelSelectionBox() const { return mLabelSelectionBox; } // public property members: QCPAxis::AxisType type; QPen basePen; QCPLineEnding lowerEnding, upperEnding; // directly accessed by QCPAxis setters/getters int labelPadding; // directly accessed by QCPAxis setters/getters QFont labelFont; QColor labelColor; QString label; int tickLabelPadding; // directly accessed by QCPAxis setters/getters double tickLabelRotation; // directly accessed by QCPAxis setters/getters QCPAxis::LabelSide tickLabelSide; // directly accessed by QCPAxis setters/getters bool substituteExponent; bool numberMultiplyCross; // directly accessed by QCPAxis setters/getters int tickLengthIn, tickLengthOut, subTickLengthIn, subTickLengthOut; // directly accessed by QCPAxis setters/getters QPen tickPen, subTickPen; QFont tickLabelFont; QColor tickLabelColor; QRect axisRect, viewportRect; double offset; // directly accessed by QCPAxis setters/getters bool abbreviateDecimalPowers; bool reversedEndings; QVector subTickPositions; QVector tickPositions; QVector tickLabels; protected: struct CachedLabel { QPointF offset; QPixmap pixmap; }; struct TickLabelData { QString basePart, expPart, suffixPart; QRect baseBounds, expBounds, suffixBounds, totalBounds, rotatedTotalBounds; QFont baseFont, expFont; }; QCustomPlot *mParentPlot; QByteArray mLabelParameterHash; // to determine whether mLabelCache needs to be cleared due to changed parameters QCache mLabelCache; QRect mAxisSelectionBox, mTickLabelsSelectionBox, mLabelSelectionBox; virtual QByteArray generateLabelParameterHash() const; virtual void placeTickLabel(QCPPainter *painter, double position, int distanceToAxis, const QString &text, QSize *tickLabelsSize); virtual void drawTickLabel(QCPPainter *painter, double x, double y, const TickLabelData &labelData) const; virtual TickLabelData getTickLabelData(const QFont &font, const QString &text) const; virtual QPointF getTickLabelDrawOffset(const TickLabelData &labelData) const; virtual void getMaxTickLabelSize(const QFont &font, const QString &text, QSize *tickLabelsSize) const; }; /* end of 'src/axis/axis.h' */ /* including file 'src/scatterstyle.h', size 7275 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPScatterStyle { Q_GADGET public: /*! Represents the various properties of a scatter style instance. For example, this enum is used to specify which properties of \ref QCPSelectionDecorator::setScatterStyle will be used when highlighting selected data points. Specific scatter properties can be transferred between \ref QCPScatterStyle instances via \ref setFromOther. */ enum ScatterProperty { spNone = 0x00 ///< 0x00 None , spPen = 0x01 ///< 0x01 The pen property, see \ref setPen , spBrush = 0x02 ///< 0x02 The brush property, see \ref setBrush , spSize = 0x04 ///< 0x04 The size property, see \ref setSize , spShape = 0x08 ///< 0x08 The shape property, see \ref setShape , spAll = 0xFF ///< 0xFF All properties }; Q_ENUM(ScatterProperty) Q_FLAGS(ScatterProperties) Q_DECLARE_FLAGS(ScatterProperties, ScatterProperty) /*! Defines the shape used for scatter points. On plottables/items that draw scatters, the sizes of these visualizations (with exception of \ref ssDot and \ref ssPixmap) can be controlled with the \ref setSize function. Scatters are drawn with the pen and brush specified with \ref setPen and \ref setBrush. */ enum ScatterShape { ssNone ///< no scatter symbols are drawn (e.g. in QCPGraph, data only represented with lines) , ssDot ///< \enumimage{ssDot.png} a single pixel (use \ref ssDisc or \ref ssCircle if you want a round shape with a certain radius) , ssCross ///< \enumimage{ssCross.png} a cross , ssPlus ///< \enumimage{ssPlus.png} a plus , ssCircle ///< \enumimage{ssCircle.png} a circle , ssDisc ///< \enumimage{ssDisc.png} a circle which is filled with the pen's color (not the brush as with ssCircle) , ssSquare ///< \enumimage{ssSquare.png} a square , ssDiamond ///< \enumimage{ssDiamond.png} a diamond , ssStar ///< \enumimage{ssStar.png} a star with eight arms, i.e. a combination of cross and plus , ssTriangle ///< \enumimage{ssTriangle.png} an equilateral triangle, standing on baseline , ssTriangleInverted ///< \enumimage{ssTriangleInverted.png} an equilateral triangle, standing on corner , ssCrossSquare ///< \enumimage{ssCrossSquare.png} a square with a cross inside , ssPlusSquare ///< \enumimage{ssPlusSquare.png} a square with a plus inside , ssCrossCircle ///< \enumimage{ssCrossCircle.png} a circle with a cross inside , ssPlusCircle ///< \enumimage{ssPlusCircle.png} a circle with a plus inside , ssPeace ///< \enumimage{ssPeace.png} a circle, with one vertical and two downward diagonal lines , ssPixmap ///< a custom pixmap specified by \ref setPixmap, centered on the data point coordinates , ssCustom ///< custom painter operations are performed per scatter (As QPainterPath, see \ref setCustomPath) }; Q_ENUM(ScatterShape) QCPScatterStyle(); QCPScatterStyle(ScatterShape shape, double size = 6); QCPScatterStyle(ScatterShape shape, const QColor &color, double size); QCPScatterStyle(ScatterShape shape, const QColor &color, const QColor &fill, double size); QCPScatterStyle(ScatterShape shape, const QPen &pen, const QBrush &brush, double size); QCPScatterStyle(const QPixmap &pixmap); QCPScatterStyle(const QPainterPath &customPath, const QPen &pen, const QBrush &brush = Qt::NoBrush, double size = 6); // getters: double size() const { return mSize; } ScatterShape shape() const { return mShape; } QPen pen() const { return mPen; } QBrush brush() const { return mBrush; } QPixmap pixmap() const { return mPixmap; } QPainterPath customPath() const { return mCustomPath; } // setters: void setFromOther(const QCPScatterStyle &other, ScatterProperties properties); void setSize(double size); void setShape(ScatterShape shape); void setPen(const QPen &pen); void setBrush(const QBrush &brush); void setPixmap(const QPixmap &pixmap); void setCustomPath(const QPainterPath &customPath); // non-property methods: bool isNone() const { return mShape == ssNone; } bool isPenDefined() const { return mPenDefined; } void undefinePen(); void applyTo(QCPPainter *painter, const QPen &defaultPen) const; void drawShape(QCPPainter *painter, const QPointF &pos) const; void drawShape(QCPPainter *painter, double x, double y) const; protected: // property members: double mSize; ScatterShape mShape; QPen mPen; QBrush mBrush; QPixmap mPixmap; QPainterPath mCustomPath; // non-property members: bool mPenDefined; }; Q_DECLARE_TYPEINFO(QCPScatterStyle, Q_MOVABLE_TYPE); Q_DECLARE_OPERATORS_FOR_FLAGS(QCPScatterStyle::ScatterProperties) Q_DECLARE_METATYPE(QCPScatterStyle::ScatterProperty) Q_DECLARE_METATYPE(QCPScatterStyle::ScatterShape) /* end of 'src/scatterstyle.h' */ /* including file 'src/datacontainer.h', size 4535 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ /*! \relates QCPDataContainer Returns whether the sort key of \a a is less than the sort key of \a b. \see QCPDataContainer::sort */ template inline bool qcpLessThanSortKey(const DataType &a, const DataType &b) { return a.sortKey() < b.sortKey(); } template class QCP_LIB_DECL QCPDataContainer { public: typedef typename QVector::const_iterator const_iterator; typedef typename QVector::iterator iterator; QCPDataContainer(); // getters: int size() const { return mData.size() - mPreallocSize; } bool isEmpty() const { return size() == 0; } bool autoSqueeze() const { return mAutoSqueeze; } // setters: void setAutoSqueeze(bool enabled); // non-virtual methods: void set(const QCPDataContainer &data); void set(const QVector &data, bool alreadySorted = false); void add(const QCPDataContainer &data); void add(const QVector &data, bool alreadySorted = false); void add(const DataType &data); void removeBefore(double sortKey); void removeAfter(double sortKey); void remove(double sortKeyFrom, double sortKeyTo); void remove(double sortKey); void clear(); void sort(); void squeeze(bool preAllocation = true, bool postAllocation = true); const_iterator constBegin() const { return mData.constBegin() + mPreallocSize; } const_iterator constEnd() const { return mData.constEnd(); } iterator begin() { return mData.begin() + mPreallocSize; } iterator end() { return mData.end(); } const_iterator findBegin(double sortKey, bool expandedRange = true) const; const_iterator findEnd(double sortKey, bool expandedRange = true) const; const_iterator at(int index) const { return constBegin() + qBound(0, index, size()); } QCPRange keyRange(bool &foundRange, QCP::SignDomain signDomain = QCP::sdBoth); QCPRange valueRange(bool &foundRange, QCP::SignDomain signDomain = QCP::sdBoth, const QCPRange &inKeyRange = QCPRange()); QCPDataRange dataRange() const { return QCPDataRange(0, size()); } void limitIteratorsToDataRange(const_iterator &begin, const_iterator &end, const QCPDataRange &dataRange) const; protected: // property members: bool mAutoSqueeze; // non-property memebers: QVector mData; int mPreallocSize; int mPreallocIteration; // non-virtual methods: void preallocateGrow(int minimumPreallocSize); void performAutoSqueeze(); }; // include implementation in header since it is a class template: /* including file 'src/datacontainer.cpp', size 31224 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPDataContainer //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPDataContainer \brief The generic data container for one-dimensional plottables This class template provides a fast container for data storage of one-dimensional data. The data type is specified as template parameter (called \a DataType in the following) and must provide some methods as described in the \ref qcpdatacontainer-datatype "next section". The data is stored in a sorted fashion, which allows very quick lookups by the sorted key as well as retrieval of ranges (see \ref findBegin, \ref findEnd, \ref keyRange) using binary search. The container uses a preallocation and a postallocation scheme, such that appending and prepending data (with respect to the sort key) is very fast and minimizes reallocations. If data is added which needs to be inserted between existing keys, the merge usually can be done quickly too, using the fact that existing data is always sorted. The user can further improve performance by specifying that added data is already itself sorted by key, if he can guarantee that this is the case (see for example \ref add(const QVector &data, bool alreadySorted)). The data can be accessed with the provided const iterators (\ref constBegin, \ref constEnd). If it is necessary to alter existing data in-place, the non-const iterators can be used (\ref begin, \ref end). Changing data members that are not the sort key (for most data types called \a key) is safe from the container's perspective. Great care must be taken however if the sort key is modified through the non-const iterators. For performance reasons, the iterators don't automatically cause a re-sorting upon their manipulation. It is thus the responsibility of the user to leave the container in a sorted state when finished with the data manipulation, before calling any other methods on the container. A complete re-sort (e.g. after finishing all sort key manipulation) can be done by calling \ref sort. Failing to do so can not be detected by the container efficiently and will cause both rendering artifacts and potential data loss. Implementing one-dimensional plottables that make use of a \ref QCPDataContainer is usually done by subclassing from \ref QCPAbstractPlottable1D "QCPAbstractPlottable1D", which introduces an according \a mDataContainer member and some convenience methods. \section qcpdatacontainer-datatype Requirements for the DataType template parameter The template parameter DataType is the type of the stored data points. It must be trivially copyable and have the following public methods, preferably inline: \li double sortKey() const\n Returns the member variable of this data point that is the sort key, defining the ordering in the container. Often this variable is simply called \a key. \li static DataType fromSortKey(double sortKey)\n Returns a new instance of the data type initialized with its sort key set to \a sortKey. \li static bool sortKeyIsMainKey()\n Returns true if the sort key is equal to the main key (see method \c mainKey below). For most plottables this is the case. It is not the case for example for \ref QCPCurve, which uses \a t as sort key and \a key as main key. This is the reason why QCPCurve unlike QCPGraph can display parametric curves with loops. \li double mainKey() const\n Returns the variable of this data point considered the main key. This is commonly the variable that is used as the coordinate of this data point on the key axis of the plottable. This method is used for example when determining the automatic axis rescaling of key axes (\ref QCPAxis::rescale). \li double mainValue() const\n Returns the variable of this data point considered the main value. This is commonly the variable that is used as the coordinate of this data point on the value axis of the plottable. \li QCPRange valueRange() const\n Returns the range this data point spans in the value axis coordinate. If the data is single-valued (e.g. QCPGraphData), this is simply a range with both lower and upper set to the main data point value. However if the data points can represent multiple values at once (e.g QCPFinancialData with its \a high, \a low, \a open and \a close values at each \a key) this method should return the range those values span. This method is used for example when determining the automatic axis rescaling of value axes (\ref QCPAxis::rescale). */ /* start documentation of inline functions */ /*! \fn int QCPDataContainer::size() const Returns the number of data points in the container. */ /*! \fn bool QCPDataContainer::isEmpty() const Returns whether this container holds no data points. */ /*! \fn QCPDataContainer::const_iterator QCPDataContainer::constBegin() const Returns a const iterator to the first data point in this container. */ /*! \fn QCPDataContainer::const_iterator QCPDataContainer::constEnd() const Returns a const iterator to the element past the last data point in this container. */ /*! \fn QCPDataContainer::iterator QCPDataContainer::begin() const Returns a non-const iterator to the first data point in this container. You can manipulate the data points in-place through the non-const iterators, but great care must be taken when manipulating the sort key of a data point, see \ref sort, or the detailed description of this class. */ /*! \fn QCPDataContainer::iterator QCPDataContainer::end() const Returns a non-const iterator to the element past the last data point in this container. You can manipulate the data points in-place through the non-const iterators, but great care must be taken when manipulating the sort key of a data point, see \ref sort, or the detailed description of this class. */ /*! \fn QCPDataContainer::const_iterator QCPDataContainer::at(int index) const Returns a const iterator to the element with the specified \a index. If \a index points beyond the available elements in this container, returns \ref constEnd, i.e. an iterator past the last valid element. You can use this method to easily obtain iterators from a \ref QCPDataRange, see the \ref dataselection-accessing "data selection page" for an example. */ /*! \fn QCPDataRange QCPDataContainer::dataRange() const Returns a \ref QCPDataRange encompassing the entire data set of this container. This means the begin index of the returned range is 0, and the end index is \ref size. */ /* end documentation of inline functions */ /*! Constructs a QCPDataContainer used for plottable classes that represent a series of key-sorted data */ template QCPDataContainer::QCPDataContainer() : mAutoSqueeze(true), mPreallocSize(0), mPreallocIteration(0) { } /*! Sets whether the container automatically decides when to release memory from its post- and preallocation pools when data points are removed. By default this is enabled and for typical applications shouldn't be changed. If auto squeeze is disabled, you can manually decide when to release pre-/postallocation with \ref squeeze. */ template void QCPDataContainer::setAutoSqueeze(bool enabled) { if (mAutoSqueeze != enabled) { mAutoSqueeze = enabled; if (mAutoSqueeze) performAutoSqueeze(); } } /*! \overload Replaces the current data in this container with the provided \a data. \see add, remove */ template void QCPDataContainer::set(const QCPDataContainer &data) { clear(); add(data); } /*! \overload Replaces the current data in this container with the provided \a data If you can guarantee that the data points in \a data have ascending order with respect to the DataType's sort key, set \a alreadySorted to true to avoid an unnecessary sorting run. \see add, remove */ template void QCPDataContainer::set(const QVector &data, bool alreadySorted) { mData = data; mPreallocSize = 0; mPreallocIteration = 0; if (!alreadySorted) sort(); } /*! \overload Adds the provided \a data to the current data in this container. \see set, remove */ template void QCPDataContainer::add(const QCPDataContainer &data) { if (data.isEmpty()) return; const int n = data.size(); const int oldSize = size(); if (oldSize > 0 && !qcpLessThanSortKey( *constBegin(), *(data.constEnd() - 1))) // prepend if new data keys are all smaller than or equal to existing ones { if (mPreallocSize < n) preallocateGrow(n); mPreallocSize -= n; std::copy(data.constBegin(), data.constEnd(), begin()); } else // don't need to prepend, so append and merge if necessary { mData.resize(mData.size() + n); std::copy(data.constBegin(), data.constEnd(), end() - n); if (oldSize > 0 && !qcpLessThanSortKey( *(constEnd() - n - 1), *(constEnd() - n))) // if appended range keys aren't all greater than existing ones, merge the two partitions std::inplace_merge(begin(), end() - n, end(), qcpLessThanSortKey); } } /*! Adds the provided data points in \a data to the current data. If you can guarantee that the data points in \a data have ascending order with respect to the DataType's sort key, set \a alreadySorted to true to avoid an unnecessary sorting run. \see set, remove */ template void QCPDataContainer::add(const QVector &data, bool alreadySorted) { if (data.isEmpty()) return; if (isEmpty()) { set(data, alreadySorted); return; } const int n = data.size(); const int oldSize = size(); if (alreadySorted && oldSize > 0 && !qcpLessThanSortKey( *constBegin(), *(data.constEnd() - 1))) // prepend if new data is sorted and keys are all smaller than or equal to existing ones { if (mPreallocSize < n) preallocateGrow(n); mPreallocSize -= n; std::copy(data.constBegin(), data.constEnd(), begin()); } else // don't need to prepend, so append and then sort and merge if necessary { mData.resize(mData.size() + n); std::copy(data.constBegin(), data.constEnd(), end() - n); if (!alreadySorted) // sort appended subrange if it wasn't already sorted std::sort(end() - n, end(), qcpLessThanSortKey); if (oldSize > 0 && !qcpLessThanSortKey( *(constEnd() - n - 1), *(constEnd() - n))) // if appended range keys aren't all greater than existing ones, merge the two partitions std::inplace_merge(begin(), end() - n, end(), qcpLessThanSortKey); } } /*! \overload Adds the provided single data point to the current data. \see remove */ template void QCPDataContainer::add(const DataType &data) { if (isEmpty() || !qcpLessThanSortKey( data, *(constEnd() - 1))) // quickly handle appends if new data key is greater or equal to existing ones { mData.append(data); } else if (qcpLessThanSortKey(data, *constBegin())) // quickly handle prepends using preallocated space { if (mPreallocSize < 1) preallocateGrow(1); --mPreallocSize; *begin() = data; } else // handle inserts, maintaining sorted keys { QCPDataContainer::iterator insertionPoint = std::lower_bound(begin(), end(), data, qcpLessThanSortKey); mData.insert(insertionPoint, data); } } /*! Removes all data points with (sort-)keys smaller than or equal to \a sortKey. \see removeAfter, remove, clear */ template void QCPDataContainer::removeBefore(double sortKey) { QCPDataContainer::iterator it = begin(); QCPDataContainer::iterator itEnd = std::lower_bound(begin(), end(), DataType::fromSortKey(sortKey), qcpLessThanSortKey); mPreallocSize += itEnd - it; // don't actually delete, just add it to the preallocated block (if it gets too large, squeeze will take care of it) if (mAutoSqueeze) performAutoSqueeze(); } /*! Removes all data points with (sort-)keys greater than or equal to \a sortKey. \see removeBefore, remove, clear */ template void QCPDataContainer::removeAfter(double sortKey) { QCPDataContainer::iterator it = std::upper_bound(begin(), end(), DataType::fromSortKey(sortKey), qcpLessThanSortKey); QCPDataContainer::iterator itEnd = end(); mData.erase(it, itEnd); // typically adds it to the postallocated block if (mAutoSqueeze) performAutoSqueeze(); } /*! Removes all data points with (sort-)keys between \a sortKeyFrom and \a sortKeyTo. if \a sortKeyFrom is greater or equal to \a sortKeyTo, the function does nothing. To remove a single data point with known (sort-)key, use \ref remove(double sortKey). \see removeBefore, removeAfter, clear */ template void QCPDataContainer::remove(double sortKeyFrom, double sortKeyTo) { if (sortKeyFrom >= sortKeyTo || isEmpty()) return; QCPDataContainer::iterator it = std::lower_bound(begin(), end(), DataType::fromSortKey(sortKeyFrom), qcpLessThanSortKey); QCPDataContainer::iterator itEnd = std::upper_bound(it, end(), DataType::fromSortKey(sortKeyTo), qcpLessThanSortKey); mData.erase(it, itEnd); if (mAutoSqueeze) performAutoSqueeze(); } /*! \overload Removes a single data point at \a sortKey. If the position is not known with absolute (binary) precision, consider using \ref remove(double sortKeyFrom, double sortKeyTo) with a small fuzziness interval around the suspected position, depeding on the precision with which the (sort-)key is known. \see removeBefore, removeAfter, clear */ template void QCPDataContainer::remove(double sortKey) { QCPDataContainer::iterator it = std::lower_bound(begin(), end(), DataType::fromSortKey(sortKey), qcpLessThanSortKey); if (it != end() && it->sortKey() == sortKey) { if (it == begin()) ++mPreallocSize; // don't actually delete, just add it to the preallocated block (if it gets too large, squeeze will take care of it) else mData.erase(it); } if (mAutoSqueeze) performAutoSqueeze(); } /*! Removes all data points. \see remove, removeAfter, removeBefore */ template void QCPDataContainer::clear() { mData.clear(); mPreallocIteration = 0; mPreallocSize = 0; } /*! Re-sorts all data points in the container by their sort key. When setting, adding or removing points using the QCPDataContainer interface (\ref set, \ref add, \ref remove, etc.), the container makes sure to always stay in a sorted state such that a full resort is never necessary. However, if you choose to directly manipulate the sort key on data points by accessing and modifying it through the non-const iterators (\ref begin, \ref end), it is your responsibility to bring the container back into a sorted state before any other methods are called on it. This can be achieved by calling this method immediately after finishing the sort key manipulation. */ template void QCPDataContainer::sort() { std::sort(begin(), end(), qcpLessThanSortKey); } /*! Frees all unused memory that is currently in the preallocation and postallocation pools. Note that QCPDataContainer automatically decides whether squeezing is necessary, if \ref setAutoSqueeze is left enabled. It should thus not be necessary to use this method for typical applications. The parameters \a preAllocation and \a postAllocation control whether pre- and/or post allocation should be freed, respectively. */ template void QCPDataContainer::squeeze(bool preAllocation, bool postAllocation) { if (preAllocation) { if (mPreallocSize > 0) { std::copy(begin(), end(), mData.begin()); mData.resize(size()); mPreallocSize = 0; } mPreallocIteration = 0; } if (postAllocation) mData.squeeze(); } /*! Returns an iterator to the data point with a (sort-)key that is equal to, just below, or just above \a sortKey. If \a expandedRange is true, the data point just below \a sortKey will be considered, otherwise the one just above. This can be used in conjunction with \ref findEnd to iterate over data points within a given key range, including or excluding the bounding data points that are just beyond the specified range. If \a expandedRange is true but there are no data points below \a sortKey, \ref constBegin is returned. If the container is empty, returns \ref constEnd. \see findEnd, QCPPlottableInterface1D::findBegin */ template typename QCPDataContainer::const_iterator QCPDataContainer::findBegin(double sortKey, bool expandedRange) const { if (isEmpty()) return constEnd(); QCPDataContainer::const_iterator it = std::lower_bound(constBegin(), constEnd(), DataType::fromSortKey(sortKey), qcpLessThanSortKey); if (expandedRange && it != constBegin()) // also covers it == constEnd case, and we know --constEnd is valid because mData isn't empty --it; return it; } /*! Returns an iterator to the element after the data point with a (sort-)key that is equal to, just above or just below \a sortKey. If \a expandedRange is true, the data point just above \a sortKey will be considered, otherwise the one just below. This can be used in conjunction with \ref findBegin to iterate over data points within a given key range, including the bounding data points that are just below and above the specified range. If \a expandedRange is true but there are no data points above \a sortKey, \ref constEnd is returned. If the container is empty, \ref constEnd is returned. \see findBegin, QCPPlottableInterface1D::findEnd */ template typename QCPDataContainer::const_iterator QCPDataContainer::findEnd(double sortKey, bool expandedRange) const { if (isEmpty()) return constEnd(); QCPDataContainer::const_iterator it = std::upper_bound(constBegin(), constEnd(), DataType::fromSortKey(sortKey), qcpLessThanSortKey); if (expandedRange && it != constEnd()) ++it; return it; } /*! Returns the range encompassed by the (main-)key coordinate of all data points. The output parameter \a foundRange indicates whether a sensible range was found. If this is false, you should not use the returned QCPRange (e.g. the data container is empty or all points have the same key). Use \a signDomain to control which sign of the key coordinates should be considered. This is relevant e.g. for logarithmic plots which can mathematically only display one sign domain at a time. If the DataType reports that its main key is equal to the sort key (\a sortKeyIsMainKey), as is the case for most plottables, this method uses this fact and finds the range very quickly. \see valueRange */ template QCPRange QCPDataContainer::keyRange(bool &foundRange, QCP::SignDomain signDomain) { if (isEmpty()) { foundRange = false; return QCPRange(); } QCPRange range; bool haveLower = false; bool haveUpper = false; double current; QCPDataContainer::const_iterator it = constBegin(); QCPDataContainer::const_iterator itEnd = constEnd(); if (signDomain == QCP::sdBoth) // range may be anywhere { if (DataType:: sortKeyIsMainKey()) // if DataType is sorted by main key (e.g. QCPGraph, but not QCPCurve), use faster algorithm by finding just first and last key with non-NaN value { while (it != itEnd) // find first non-nan going up from left { if (!qIsNaN(it->mainValue())) { range.lower = it->mainKey(); haveLower = true; break; } ++it; } it = itEnd; while (it != constBegin()) // find first non-nan going down from right { --it; if (!qIsNaN(it->mainValue())) { range.upper = it->mainKey(); haveUpper = true; break; } } } else // DataType is not sorted by main key, go through all data points and accordingly expand range { while (it != itEnd) { if (!qIsNaN(it->mainValue())) { current = it->mainKey(); if (current < range.lower || !haveLower) { range.lower = current; haveLower = true; } if (current > range.upper || !haveUpper) { range.upper = current; haveUpper = true; } } ++it; } } } else if (signDomain == QCP::sdNegative) // range may only be in the negative sign domain { while (it != itEnd) { if (!qIsNaN(it->mainValue())) { current = it->mainKey(); if ((current < range.lower || !haveLower) && current < 0) { range.lower = current; haveLower = true; } if ((current > range.upper || !haveUpper) && current < 0) { range.upper = current; haveUpper = true; } } ++it; } } else if (signDomain == QCP::sdPositive) // range may only be in the positive sign domain { while (it != itEnd) { if (!qIsNaN(it->mainValue())) { current = it->mainKey(); if ((current < range.lower || !haveLower) && current > 0) { range.lower = current; haveLower = true; } if ((current > range.upper || !haveUpper) && current > 0) { range.upper = current; haveUpper = true; } } ++it; } } foundRange = haveLower && haveUpper; return range; } /*! Returns the range encompassed by the value coordinates of the data points in the specified key range (\a inKeyRange), using the full \a DataType::valueRange reported by the data points. The output parameter \a foundRange indicates whether a sensible range was found. If this is false, you should not use the returned QCPRange (e.g. the data container is empty or all points have the same value). If \a inKeyRange has both lower and upper bound set to zero (is equal to QCPRange()), all data points are considered, without any restriction on the keys. Use \a signDomain to control which sign of the value coordinates should be considered. This is relevant e.g. for logarithmic plots which can mathematically only display one sign domain at a time. \see keyRange */ template QCPRange QCPDataContainer::valueRange(bool &foundRange, QCP::SignDomain signDomain, const QCPRange &inKeyRange) { if (isEmpty()) { foundRange = false; return QCPRange(); } QCPRange range; const bool restrictKeyRange = inKeyRange != QCPRange(); bool haveLower = false; bool haveUpper = false; QCPRange current; QCPDataContainer::const_iterator itBegin = constBegin(); QCPDataContainer::const_iterator itEnd = constEnd(); if (DataType::sortKeyIsMainKey() && restrictKeyRange) { itBegin = findBegin(inKeyRange.lower); itEnd = findEnd(inKeyRange.upper); } if (signDomain == QCP::sdBoth) // range may be anywhere { for (QCPDataContainer::const_iterator it = itBegin; it != itEnd; ++it) { if (restrictKeyRange && (it->mainKey() < inKeyRange.lower || it->mainKey() > inKeyRange.upper)) continue; current = it->valueRange(); if ((current.lower < range.lower || !haveLower) && !qIsNaN(current.lower)) { range.lower = current.lower; haveLower = true; } if ((current.upper > range.upper || !haveUpper) && !qIsNaN(current.upper)) { range.upper = current.upper; haveUpper = true; } } } else if (signDomain == QCP::sdNegative) // range may only be in the negative sign domain { for (QCPDataContainer::const_iterator it = itBegin; it != itEnd; ++it) { if (restrictKeyRange && (it->mainKey() < inKeyRange.lower || it->mainKey() > inKeyRange.upper)) continue; current = it->valueRange(); if ((current.lower < range.lower || !haveLower) && current.lower < 0 && !qIsNaN(current.lower)) { range.lower = current.lower; haveLower = true; } if ((current.upper > range.upper || !haveUpper) && current.upper < 0 && !qIsNaN(current.upper)) { range.upper = current.upper; haveUpper = true; } } } else if (signDomain == QCP::sdPositive) // range may only be in the positive sign domain { for (QCPDataContainer::const_iterator it = itBegin; it != itEnd; ++it) { if (restrictKeyRange && (it->mainKey() < inKeyRange.lower || it->mainKey() > inKeyRange.upper)) continue; current = it->valueRange(); if ((current.lower < range.lower || !haveLower) && current.lower > 0 && !qIsNaN(current.lower)) { range.lower = current.lower; haveLower = true; } if ((current.upper > range.upper || !haveUpper) && current.upper > 0 && !qIsNaN(current.upper)) { range.upper = current.upper; haveUpper = true; } } } foundRange = haveLower && haveUpper; return range; } /*! Makes sure \a begin and \a end mark a data range that is both within the bounds of this data container's data, as well as within the specified \a dataRange. This function doesn't require for \a dataRange to be within the bounds of this data container's valid range. */ template void QCPDataContainer::limitIteratorsToDataRange(const_iterator &begin, const_iterator &end, const QCPDataRange &dataRange) const { QCPDataRange iteratorRange(begin - constBegin(), end - constBegin()); iteratorRange = iteratorRange.bounded(dataRange.bounded(this->dataRange())); begin = constBegin() + iteratorRange.begin(); end = constBegin() + iteratorRange.end(); } /*! \internal Increases the preallocation pool to have a size of at least \a minimumPreallocSize. Depending on the preallocation history, the container will grow by more than requested, to speed up future consecutive size increases. if \a minimumPreallocSize is smaller than or equal to the current preallocation pool size, this method does nothing. */ template void QCPDataContainer::preallocateGrow(int minimumPreallocSize) { if (minimumPreallocSize <= mPreallocSize) return; int newPreallocSize = minimumPreallocSize; newPreallocSize += (1u << qBound(4, mPreallocIteration + 4, 15)) - 12; // do 4 up to 32768-12 preallocation, doubling in each intermediate iteration ++mPreallocIteration; int sizeDifference = newPreallocSize - mPreallocSize; mData.resize(mData.size() + sizeDifference); std::copy_backward(mData.begin() + mPreallocSize, mData.end() - sizeDifference, mData.end()); mPreallocSize = newPreallocSize; } /*! \internal This method decides, depending on the total allocation size and the size of the unused pre- and postallocation pools, whether it is sensible to reduce the pools in order to free up unused memory. It then possibly calls \ref squeeze to do the deallocation. If \ref setAutoSqueeze is enabled, this method is called automatically each time data points are removed from the container (e.g. \ref remove). \note when changing the decision parameters, care must be taken not to cause a back-and-forth between squeezing and reallocation due to the growth strategy of the internal QVector and \ref preallocateGrow. The hysteresis between allocation and deallocation should be made high enough (at the expense of possibly larger unused memory from time to time). */ template void QCPDataContainer::performAutoSqueeze() { const int totalAlloc = mData.capacity(); const int postAllocSize = totalAlloc - mData.size(); const int usedSize = size(); bool shrinkPostAllocation = false; bool shrinkPreAllocation = false; if (totalAlloc > 650000) // if allocation is larger, shrink earlier with respect to total used size { shrinkPostAllocation = postAllocSize > usedSize * 1.5; // QVector grow strategy is 2^n for static data. Watch out not to oscillate! shrinkPreAllocation = mPreallocSize * 10 > usedSize; } else if (totalAlloc > 1000) // below 10 MiB raw data be generous with preallocated memory, below 1k points don't even bother { shrinkPostAllocation = postAllocSize > usedSize * 5; shrinkPreAllocation = mPreallocSize > usedSize * 1.5; // preallocation can grow into postallocation, so can be smaller } if (shrinkPreAllocation || shrinkPostAllocation) squeeze(shrinkPreAllocation, shrinkPostAllocation); } /* end of 'src/datacontainer.cpp' */ /* end of 'src/datacontainer.h' */ /* including file 'src/plottable.h', size 8312 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPSelectionDecorator { Q_GADGET public: QCPSelectionDecorator(); virtual ~QCPSelectionDecorator(); // getters: QPen pen() const { return mPen; } QBrush brush() const { return mBrush; } QCPScatterStyle scatterStyle() const { return mScatterStyle; } QCPScatterStyle::ScatterProperties usedScatterProperties() const { return mUsedScatterProperties; } // setters: void setPen(const QPen &pen); void setBrush(const QBrush &brush); void setScatterStyle(const QCPScatterStyle &scatterStyle, QCPScatterStyle::ScatterProperties usedProperties = QCPScatterStyle::spPen); void setUsedScatterProperties(const QCPScatterStyle::ScatterProperties &properties); // non-virtual methods: void applyPen(QCPPainter *painter) const; void applyBrush(QCPPainter *painter) const; QCPScatterStyle getFinalScatterStyle(const QCPScatterStyle &unselectedStyle) const; // introduced virtual methods: virtual void copyFrom(const QCPSelectionDecorator *other); virtual void drawDecoration(QCPPainter *painter, QCPDataSelection selection); protected: // property members: QPen mPen; QBrush mBrush; QCPScatterStyle mScatterStyle; QCPScatterStyle::ScatterProperties mUsedScatterProperties; // non-property members: QCPAbstractPlottable *mPlottable; // introduced virtual methods: virtual bool registerWithPlottable(QCPAbstractPlottable *plottable); private: Q_DISABLE_COPY(QCPSelectionDecorator) friend class QCPAbstractPlottable; }; Q_DECLARE_METATYPE(QCPSelectionDecorator *) class QCP_LIB_DECL QCPAbstractPlottable : public QCPLayerable { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QString name READ name WRITE setName) Q_PROPERTY(bool antialiasedFill READ antialiasedFill WRITE setAntialiasedFill) Q_PROPERTY(bool antialiasedScatters READ antialiasedScatters WRITE setAntialiasedScatters) Q_PROPERTY(QPen pen READ pen WRITE setPen) Q_PROPERTY(QBrush brush READ brush WRITE setBrush) Q_PROPERTY(QCPAxis *keyAxis READ keyAxis WRITE setKeyAxis) Q_PROPERTY(QCPAxis *valueAxis READ valueAxis WRITE setValueAxis) Q_PROPERTY(QCP::SelectionType selectable READ selectable WRITE setSelectable NOTIFY selectableChanged) Q_PROPERTY(QCPDataSelection selection READ selection WRITE setSelection NOTIFY selectionChanged) Q_PROPERTY(QCPSelectionDecorator *selectionDecorator READ selectionDecorator WRITE setSelectionDecorator) /// \endcond public: QCPAbstractPlottable(QCPAxis *keyAxis, QCPAxis *valueAxis); ~QCPAbstractPlottable() override; // getters: QString name() const { return mName; } bool antialiasedFill() const { return mAntialiasedFill; } bool antialiasedScatters() const { return mAntialiasedScatters; } QPen pen() const { return mPen; } QBrush brush() const { return mBrush; } QCPAxis *keyAxis() const { return mKeyAxis.data(); } QCPAxis *valueAxis() const { return mValueAxis.data(); } QCP::SelectionType selectable() const { return mSelectable; } bool selected() const { return !mSelection.isEmpty(); } QCPDataSelection selection() const { return mSelection; } QCPSelectionDecorator *selectionDecorator() const { return mSelectionDecorator; } // setters: void setName(const QString &name); void setAntialiasedFill(bool enabled); void setAntialiasedScatters(bool enabled); void setPen(const QPen &pen); void setBrush(const QBrush &brush); void setKeyAxis(QCPAxis *axis); void setValueAxis(QCPAxis *axis); Q_SLOT void setSelectable(QCP::SelectionType selectable); Q_SLOT void setSelection(QCPDataSelection selection); void setSelectionDecorator(QCPSelectionDecorator *decorator); // introduced virtual methods: - virtual QCPPlottableInterface1D *interface1D() { return 0; } + virtual QCPPlottableInterface1D *interface1D() { return nullptr; } virtual QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain = QCP::sdBoth) const = 0; virtual QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain = QCP::sdBoth, const QCPRange &inKeyRange = QCPRange()) const = 0; // non-property methods: void coordsToPixels(double key, double value, double &x, double &y) const; const QPointF coordsToPixels(double key, double value) const; void pixelsToCoords(double x, double y, double &key, double &value) const; void pixelsToCoords(const QPointF &pixelPos, double &key, double &value) const; void rescaleAxes(bool onlyEnlarge = false) const; void rescaleKeyAxis(bool onlyEnlarge = false) const; void rescaleValueAxis(bool onlyEnlarge = false, bool inKeyRange = false) const; bool addToLegend(QCPLegend *legend); bool addToLegend(); bool removeFromLegend(QCPLegend *legend) const; bool removeFromLegend() const; signals: void selectionChanged(bool selected); void selectionChanged(const QCPDataSelection &selection); void selectableChanged(QCP::SelectionType selectable); protected: // property members: QString mName; bool mAntialiasedFill, mAntialiasedScatters; QPen mPen; QBrush mBrush; QPointer mKeyAxis, mValueAxis; QCP::SelectionType mSelectable; QCPDataSelection mSelection; QCPSelectionDecorator *mSelectionDecorator; // reimplemented virtual methods: QRect clipRect() const override; void draw(QCPPainter *painter) override = 0; QCP::Interaction selectionCategory() const override; void applyDefaultAntialiasingHint(QCPPainter *painter) const override; // events: void selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) override; void deselectEvent(bool *selectionStateChanged) override; // introduced virtual methods: virtual void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const = 0; // non-virtual methods: void applyFillAntialiasingHint(QCPPainter *painter) const; void applyScattersAntialiasingHint(QCPPainter *painter) const; private: Q_DISABLE_COPY(QCPAbstractPlottable) friend class QCustomPlot; friend class QCPAxis; friend class QCPPlottableLegendItem; }; /* end of 'src/plottable.h' */ /* including file 'src/item.h', size 9368 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPItemAnchor { Q_GADGET public: QCPItemAnchor(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString &name, int anchorId = -1); virtual ~QCPItemAnchor(); // getters: QString name() const { return mName; } virtual QPointF pixelPosition() const; protected: // property members: QString mName; // non-property members: QCustomPlot *mParentPlot; QCPAbstractItem *mParentItem; int mAnchorId; QSet mChildrenX, mChildrenY; // introduced virtual methods: - virtual QCPItemPosition *toQCPItemPosition() { return 0; } + virtual QCPItemPosition *toQCPItemPosition() { return nullptr; } // non-virtual methods: void addChildX(QCPItemPosition *pos); // called from pos when this anchor is set as parent void removeChildX(QCPItemPosition *pos); // called from pos when its parent anchor is reset or pos deleted void addChildY(QCPItemPosition *pos); // called from pos when this anchor is set as parent void removeChildY(QCPItemPosition *pos); // called from pos when its parent anchor is reset or pos deleted private: Q_DISABLE_COPY(QCPItemAnchor) friend class QCPItemPosition; }; class QCP_LIB_DECL QCPItemPosition : public QCPItemAnchor { Q_GADGET public: /*! Defines the ways an item position can be specified. Thus it defines what the numbers passed to \ref setCoords actually mean. \see setType */ enum PositionType { ptAbsolute ///< Static positioning in pixels, starting from the top left corner of the viewport/widget. , ptViewportRatio ///< Static positioning given by a fraction of the viewport size. For example, if you call setCoords(0, 0), the position will be at the top ///< left corner of the viewport/widget. setCoords(1, 1) will be at the bottom right corner, setCoords(0.5, 0) will be horizontally centered and ///< vertically at the top of the viewport/widget, etc. , ptAxisRectRatio ///< Static positioning given by a fraction of the axis rect size (see \ref setAxisRect). For example, if you call setCoords(0, 0), the position will be at the top ///< left corner of the axis rect. setCoords(1, 1) will be at the bottom right corner, setCoords(0.5, 0) will be horizontally centered and ///< vertically at the top of the axis rect, etc. You can also go beyond the axis rect by providing negative coordinates or coordinates larger than 1. , ptPlotCoords ///< Dynamic positioning at a plot coordinate defined by two axes (see \ref setAxes). }; Q_ENUM(PositionType) QCPItemPosition(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString &name); ~QCPItemPosition() override; // getters: PositionType type() const { return typeX(); } PositionType typeX() const { return mPositionTypeX; } PositionType typeY() const { return mPositionTypeY; } QCPItemAnchor *parentAnchor() const { return parentAnchorX(); } QCPItemAnchor *parentAnchorX() const { return mParentAnchorX; } QCPItemAnchor *parentAnchorY() const { return mParentAnchorY; } double key() const { return mKey; } double value() const { return mValue; } QPointF coords() const { return QPointF(mKey, mValue); } QCPAxis *keyAxis() const { return mKeyAxis.data(); } QCPAxis *valueAxis() const { return mValueAxis.data(); } QCPAxisRect *axisRect() const; QPointF pixelPosition() const override; // setters: void setType(PositionType type); void setTypeX(PositionType type); void setTypeY(PositionType type); bool setParentAnchor(QCPItemAnchor *parentAnchor, bool keepPixelPosition = false); bool setParentAnchorX(QCPItemAnchor *parentAnchor, bool keepPixelPosition = false); bool setParentAnchorY(QCPItemAnchor *parentAnchor, bool keepPixelPosition = false); void setCoords(double key, double value); void setCoords(const QPointF &coords); void setAxes(QCPAxis *keyAxis, QCPAxis *valueAxis); void setAxisRect(QCPAxisRect *axisRect); void setPixelPosition(const QPointF &pixelPosition); protected: // property members: PositionType mPositionTypeX, mPositionTypeY; QPointer mKeyAxis, mValueAxis; QPointer mAxisRect; double mKey, mValue; QCPItemAnchor *mParentAnchorX, *mParentAnchorY; // reimplemented virtual methods: QCPItemPosition *toQCPItemPosition() override { return this; } private: Q_DISABLE_COPY(QCPItemPosition) }; Q_DECLARE_METATYPE(QCPItemPosition::PositionType) class QCP_LIB_DECL QCPAbstractItem : public QCPLayerable { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(bool clipToAxisRect READ clipToAxisRect WRITE setClipToAxisRect) Q_PROPERTY(QCPAxisRect *clipAxisRect READ clipAxisRect WRITE setClipAxisRect) Q_PROPERTY(bool selectable READ selectable WRITE setSelectable NOTIFY selectableChanged) Q_PROPERTY(bool selected READ selected WRITE setSelected NOTIFY selectionChanged) /// \endcond public: explicit QCPAbstractItem(QCustomPlot *parentPlot); ~QCPAbstractItem() override; // getters: bool clipToAxisRect() const { return mClipToAxisRect; } QCPAxisRect *clipAxisRect() const; bool selectable() const { return mSelectable; } bool selected() const { return mSelected; } // setters: void setClipToAxisRect(bool clip); void setClipAxisRect(QCPAxisRect *rect); Q_SLOT void setSelectable(bool selectable); Q_SLOT void setSelected(bool selected); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override = 0; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override = 0; // non-virtual methods: QList positions() const { return mPositions; } QList anchors() const { return mAnchors; } QCPItemPosition *position(const QString &name) const; QCPItemAnchor *anchor(const QString &name) const; bool hasAnchor(const QString &name) const; signals: void selectionChanged(bool selected); void selectableChanged(bool selectable); protected: // property members: bool mClipToAxisRect; QPointer mClipAxisRect; QList mPositions; QList mAnchors; bool mSelectable, mSelected; // reimplemented virtual methods: QCP::Interaction selectionCategory() const override; QRect clipRect() const override; void applyDefaultAntialiasingHint(QCPPainter *painter) const override; void draw(QCPPainter *painter) override = 0; // events: void selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) override; void deselectEvent(bool *selectionStateChanged) override; // introduced virtual methods: virtual QPointF anchorPixelPosition(int anchorId) const; // non-virtual methods: double rectDistance(const QRectF &rect, const QPointF &pos, bool filledRect) const; QCPItemPosition *createPosition(const QString &name); QCPItemAnchor *createAnchor(const QString &name, int anchorId); private: Q_DISABLE_COPY(QCPAbstractItem) friend class QCustomPlot; friend class QCPItemAnchor; }; /* end of 'src/item.h' */ /* including file 'src/core.h', size 14797 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCustomPlot : public QWidget { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QRect viewport READ viewport WRITE setViewport) Q_PROPERTY(QPixmap background READ background WRITE setBackground) Q_PROPERTY(bool backgroundScaled READ backgroundScaled WRITE setBackgroundScaled) Q_PROPERTY(Qt::AspectRatioMode backgroundScaledMode READ backgroundScaledMode WRITE setBackgroundScaledMode) Q_PROPERTY(QCPLayoutGrid *plotLayout READ plotLayout) Q_PROPERTY(bool autoAddPlottableToLegend READ autoAddPlottableToLegend WRITE setAutoAddPlottableToLegend) Q_PROPERTY(int selectionTolerance READ selectionTolerance WRITE setSelectionTolerance) Q_PROPERTY(bool noAntialiasingOnDrag READ noAntialiasingOnDrag WRITE setNoAntialiasingOnDrag) Q_PROPERTY(Qt::KeyboardModifier multiSelectModifier READ multiSelectModifier WRITE setMultiSelectModifier) Q_PROPERTY(bool openGl READ openGl WRITE setOpenGl) /// \endcond public: /*! Defines how a layer should be inserted relative to an other layer. \see addLayer, moveLayer */ enum LayerInsertMode { limBelow ///< Layer is inserted below other layer , limAbove ///< Layer is inserted above other layer }; Q_ENUM(LayerInsertMode) /*! Defines with what timing the QCustomPlot surface is refreshed after a replot. \see replot */ enum RefreshPriority { rpImmediateRefresh ///< Replots immediately and repaints the widget immediately by calling QWidget::repaint() after the replot , rpQueuedRefresh ///< Replots immediately, but queues the widget repaint, by calling QWidget::update() after the replot. This way multiple redundant widget repaints can be avoided. , rpRefreshHint ///< Whether to use immediate or queued refresh depends on whether the plotting hint \ref QCP::phImmediateRefresh is set, see \ref setPlottingHints. , rpQueuedReplot ///< Queues the entire replot for the next event loop iteration. This way multiple redundant replots can be avoided. The actual replot is then done with \ref rpRefreshHint priority. }; Q_ENUM(RefreshPriority) - explicit QCustomPlot(QWidget *parent = 0); + explicit QCustomPlot(QWidget *parent = nullptr); ~QCustomPlot() override; // getters: QRect viewport() const { return mViewport; } double bufferDevicePixelRatio() const { return mBufferDevicePixelRatio; } QPixmap background() const { return mBackgroundPixmap; } bool backgroundScaled() const { return mBackgroundScaled; } Qt::AspectRatioMode backgroundScaledMode() const { return mBackgroundScaledMode; } QCPLayoutGrid *plotLayout() const { return mPlotLayout; } QCP::AntialiasedElements antialiasedElements() const { return mAntialiasedElements; } QCP::AntialiasedElements notAntialiasedElements() const { return mNotAntialiasedElements; } bool autoAddPlottableToLegend() const { return mAutoAddPlottableToLegend; } const QCP::Interactions interactions() const { return mInteractions; } int selectionTolerance() const { return mSelectionTolerance; } bool noAntialiasingOnDrag() const { return mNoAntialiasingOnDrag; } QCP::PlottingHints plottingHints() const { return mPlottingHints; } Qt::KeyboardModifier multiSelectModifier() const { return mMultiSelectModifier; } QCP::SelectionRectMode selectionRectMode() const { return mSelectionRectMode; } QCPSelectionRect *selectionRect() const { return mSelectionRect; } bool openGl() const { return mOpenGl; } // setters: void setViewport(const QRect &rect); void setBufferDevicePixelRatio(double ratio); void setBackground(const QPixmap &pm); void setBackground(const QPixmap &pm, bool scaled, Qt::AspectRatioMode mode = Qt::KeepAspectRatioByExpanding); void setBackground(const QBrush &brush); void setBackgroundScaled(bool scaled); void setBackgroundScaledMode(Qt::AspectRatioMode mode); void setAntialiasedElements(const QCP::AntialiasedElements &antialiasedElements); void setAntialiasedElement(QCP::AntialiasedElement antialiasedElement, bool enabled = true); void setNotAntialiasedElements(const QCP::AntialiasedElements ¬AntialiasedElements); void setNotAntialiasedElement(QCP::AntialiasedElement notAntialiasedElement, bool enabled = true); void setAutoAddPlottableToLegend(bool on); void setInteractions(const QCP::Interactions &interactions); void setInteraction(const QCP::Interaction &interaction, bool enabled = true); void setSelectionTolerance(int pixels); void setNoAntialiasingOnDrag(bool enabled); void setPlottingHints(const QCP::PlottingHints &hints); void setPlottingHint(QCP::PlottingHint hint, bool enabled = true); void setMultiSelectModifier(Qt::KeyboardModifier modifier); void setSelectionRectMode(QCP::SelectionRectMode mode); void setSelectionRect(QCPSelectionRect *selectionRect); void setOpenGl(bool enabled, int multisampling = 16); // non-property methods: // plottable interface: QCPAbstractPlottable *plottable(int index); QCPAbstractPlottable *plottable(); bool removePlottable(QCPAbstractPlottable *plottable); bool removePlottable(int index); int clearPlottables(); int plottableCount() const; QList selectedPlottables() const; QCPAbstractPlottable *plottableAt(const QPointF &pos, bool onlySelectable = false) const; bool hasPlottable(QCPAbstractPlottable *plottable) const; // specialized interface for QCPGraph: QCPGraph *graph(int index) const; QCPGraph *graph() const; - QCPGraph *addGraph(QCPAxis *keyAxis = 0, QCPAxis *valueAxis = 0); + QCPGraph *addGraph(QCPAxis *keyAxis = nullptr, QCPAxis *valueAxis = nullptr); bool removeGraph(QCPGraph *graph); bool removeGraph(int index); int clearGraphs(); int graphCount() const; QList selectedGraphs() const; // item interface: QCPAbstractItem *item(int index) const; QCPAbstractItem *item() const; bool removeItem(QCPAbstractItem *item); bool removeItem(int index); int clearItems(); int itemCount() const; QList selectedItems() const; QCPAbstractItem *itemAt(const QPointF &pos, bool onlySelectable = false) const; bool hasItem(QCPAbstractItem *item) const; // layer interface: QCPLayer *layer(const QString &name) const; QCPLayer *layer(int index) const; QCPLayer *currentLayer() const; bool setCurrentLayer(const QString &name); bool setCurrentLayer(QCPLayer *layer); int layerCount() const; - bool addLayer(const QString &name, QCPLayer *otherLayer = 0, LayerInsertMode insertMode = limAbove); + bool addLayer(const QString &name, QCPLayer *otherLayer = nullptr, LayerInsertMode insertMode = limAbove); bool removeLayer(QCPLayer *layer); bool moveLayer(QCPLayer *layer, QCPLayer *otherLayer, LayerInsertMode insertMode = limAbove); // axis rect/layout interface: int axisRectCount() const; QCPAxisRect *axisRect(int index = 0) const; QList axisRects() const; QCPLayoutElement *layoutElementAt(const QPointF &pos) const; QCPAxisRect *axisRectAt(const QPointF &pos) const; Q_SLOT void rescaleAxes(bool onlyVisiblePlottables = false); QList selectedAxes() const; QList selectedLegends() const; Q_SLOT void deselectAll(); bool savePdf(const QString &fileName, int width = 0, int height = 0, QCP::ExportPen exportPen = QCP::epAllowCosmetic, const QString &pdfCreator = QString(), const QString &pdfTitle = QString()); bool savePng(const QString &fileName, int width = 0, int height = 0, double scale = 1.0, int quality = -1, int resolution = 96, QCP::ResolutionUnit resolutionUnit = QCP::ruDotsPerInch); bool saveJpg(const QString &fileName, int width = 0, int height = 0, double scale = 1.0, int quality = -1, int resolution = 96, QCP::ResolutionUnit resolutionUnit = QCP::ruDotsPerInch); bool saveBmp(const QString &fileName, int width = 0, int height = 0, double scale = 1.0, int resolution = 96, QCP::ResolutionUnit resolutionUnit = QCP::ruDotsPerInch); bool saveRastered(const QString &fileName, int width, int height, double scale, const char *format, int quality = -1, int resolution = 96, QCP::ResolutionUnit resolutionUnit = QCP::ruDotsPerInch); QPixmap toPixmap(int width = 0, int height = 0, double scale = 1.0); void toPainter(QCPPainter *painter, int width = 0, int height = 0); Q_SLOT void replot(QCustomPlot::RefreshPriority refreshPriority = QCustomPlot::rpRefreshHint); QCPAxis *xAxis, *yAxis, *xAxis2, *yAxis2; QCPLegend *legend; signals: void mouseDoubleClick(QMouseEvent *event); void mousePress(QMouseEvent *event); void mouseMove(QMouseEvent *event); void mouseRelease(QMouseEvent *event); void mouseWheel(QWheelEvent *event); void plottableClick(QCPAbstractPlottable *plottable, int dataIndex, QMouseEvent *event); void plottableDoubleClick(QCPAbstractPlottable *plottable, int dataIndex, QMouseEvent *event); void itemClick(QCPAbstractItem *item, QMouseEvent *event); void itemDoubleClick(QCPAbstractItem *item, QMouseEvent *event); void axisClick(QCPAxis *axis, QCPAxis::SelectablePart part, QMouseEvent *event); void axisDoubleClick(QCPAxis *axis, QCPAxis::SelectablePart part, QMouseEvent *event); void legendClick(QCPLegend *legend, QCPAbstractLegendItem *item, QMouseEvent *event); void legendDoubleClick(QCPLegend *legend, QCPAbstractLegendItem *item, QMouseEvent *event); void selectionChangedByUser(); void beforeReplot(); void afterReplot(); protected: // property members: QRect mViewport; double mBufferDevicePixelRatio; QCPLayoutGrid *mPlotLayout; bool mAutoAddPlottableToLegend; QList mPlottables; QList mGraphs; // extra list of plottables also in mPlottables that are of type QCPGraph QList mItems; QList mLayers; QCP::AntialiasedElements mAntialiasedElements, mNotAntialiasedElements; QCP::Interactions mInteractions; int mSelectionTolerance; bool mNoAntialiasingOnDrag; QBrush mBackgroundBrush; QPixmap mBackgroundPixmap; QPixmap mScaledBackgroundPixmap; bool mBackgroundScaled; Qt::AspectRatioMode mBackgroundScaledMode; QCPLayer *mCurrentLayer; QCP::PlottingHints mPlottingHints; Qt::KeyboardModifier mMultiSelectModifier; QCP::SelectionRectMode mSelectionRectMode; QCPSelectionRect *mSelectionRect; bool mOpenGl; // non-property members: QList> mPaintBuffers; QPoint mMousePressPos; bool mMouseHasMoved; QPointer mMouseEventLayerable; QVariant mMouseEventLayerableDetails; bool mReplotting; bool mReplotQueued; int mOpenGlMultisamples; QCP::AntialiasedElements mOpenGlAntialiasedElementsBackup; bool mOpenGlCacheLabelsBackup; #ifdef QCP_OPENGL_FBO QSharedPointer mGlContext; QSharedPointer mGlSurface; QSharedPointer mGlPaintDevice; #endif // reimplemented virtual methods: QSize minimumSizeHint() const override; QSize sizeHint() const override; void paintEvent(QPaintEvent *event) override; void resizeEvent(QResizeEvent *event) override; bool event(QEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; // introduced virtual methods: virtual void draw(QCPPainter *painter); virtual void updateLayout(); virtual void axisRemoved(QCPAxis *axis); virtual void legendRemoved(QCPLegend *legend); Q_SLOT virtual void processRectSelection(QRect rect, QMouseEvent *event); Q_SLOT virtual void processRectZoom(QRect rect, QMouseEvent *event); Q_SLOT virtual void processPointSelection(QMouseEvent *event); // non-virtual methods: bool registerPlottable(QCPAbstractPlottable *plottable); bool registerGraph(QCPGraph *graph); bool registerItem(QCPAbstractItem *item); void updateLayerIndices() const; - QCPLayerable *layerableAt(const QPointF &pos, bool onlySelectable, QVariant *selectionDetails = 0) const; + QCPLayerable *layerableAt(const QPointF &pos, bool onlySelectable, QVariant *selectionDetails = nullptr) const; QList layerableListAt(const QPointF &pos, bool onlySelectable, - QList *selectionDetails = 0) const; + QList *selectionDetails = nullptr) const; void drawBackground(QCPPainter *painter); void setupPaintBuffers(); QCPAbstractPaintBuffer *createPaintBuffer(); bool hasInvalidatedPaintBuffers(); bool setupOpenGl(); void freeOpenGl(); friend class QCPLegend; friend class QCPAxis; friend class QCPLayer; friend class QCPAxisRect; friend class QCPAbstractPlottable; friend class QCPGraph; friend class QCPAbstractItem; }; Q_DECLARE_METATYPE(QCustomPlot::LayerInsertMode) Q_DECLARE_METATYPE(QCustomPlot::RefreshPriority) /* end of 'src/core.h' */ /* including file 'src/plottable1d.h', size 4250 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPPlottableInterface1D { public: // introduced pure virtual methods: virtual int dataCount() const = 0; virtual double dataMainKey(int index) const = 0; virtual double dataSortKey(int index) const = 0; virtual double dataMainValue(int index) const = 0; virtual QCPRange dataValueRange(int index) const = 0; virtual QPointF dataPixelPosition(int index) const = 0; virtual bool sortKeyIsMainKey() const = 0; virtual QCPDataSelection selectTestRect(const QRectF &rect, bool onlySelectable) const = 0; virtual int findBegin(double sortKey, bool expandedRange = true) const = 0; virtual int findEnd(double sortKey, bool expandedRange = true) const = 0; }; template class QCP_LIB_DECL QCPAbstractPlottable1D : public QCPAbstractPlottable, public QCPPlottableInterface1D { // No Q_OBJECT macro due to template class public: QCPAbstractPlottable1D(QCPAxis *keyAxis, QCPAxis *valueAxis); ~QCPAbstractPlottable1D() override; // virtual methods of 1d plottable interface: int dataCount() const override; double dataMainKey(int index) const override; double dataSortKey(int index) const override; double dataMainValue(int index) const override; QCPRange dataValueRange(int index) const override; QPointF dataPixelPosition(int index) const override; bool sortKeyIsMainKey() const override; QCPDataSelection selectTestRect(const QRectF &rect, bool onlySelectable) const override; int findBegin(double sortKey, bool expandedRange = true) const override; int findEnd(double sortKey, bool expandedRange = true) const override; // virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; QCPPlottableInterface1D *interface1D() override { return this; } protected: // property members: QSharedPointer> mDataContainer; // helpers for subclasses: void getDataSegments(QList &selectedSegments, QList &unselectedSegments) const; void drawPolyline(QCPPainter *painter, const QVector &lineData) const; private: Q_DISABLE_COPY(QCPAbstractPlottable1D) }; // include implementation in header since it is a class template: /* including file 'src/plottable1d.cpp', size 22240 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPPlottableInterface1D //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPPlottableInterface1D \brief Defines an abstract interface for one-dimensional plottables This class contains only pure virtual methods which define a common interface to the data of one-dimensional plottables. For example, it is implemented by the template class \ref QCPAbstractPlottable1D (the preferred base class for one-dimensional plottables). So if you use that template class as base class of your one-dimensional plottable, you won't have to care about implementing the 1d interface yourself. If your plottable doesn't derive from \ref QCPAbstractPlottable1D but still wants to provide a 1d interface (e.g. like \ref QCPErrorBars does), you should inherit from both \ref QCPAbstractPlottable and \ref QCPPlottableInterface1D and accordingly reimplement the pure virtual methods of the 1d interface, matching your data container. Also, reimplement \ref QCPAbstractPlottable::interface1D to return the \c this pointer. If you have a \ref QCPAbstractPlottable pointer, you can check whether it implements this interface by calling \ref QCPAbstractPlottable::interface1D and testing it for a non-zero return value. If it indeed implements this interface, you may use it to access the plottable's data without needing to know the exact type of the plottable or its data point type. */ /* start documentation of pure virtual functions */ /*! \fn virtual int QCPPlottableInterface1D::dataCount() const = 0; Returns the number of data points of the plottable. */ /*! \fn virtual QCPDataSelection QCPPlottableInterface1D::selectTestRect(const QRectF &rect, bool onlySelectable) const = 0; Returns a data selection containing all the data points of this plottable which are contained (or hit by) \a rect. This is used mainly in the selection rect interaction for data selection (\ref dataselection "data selection mechanism"). If \a onlySelectable is true, an empty QCPDataSelection is returned if this plottable is not selectable (i.e. if \ref QCPAbstractPlottable::setSelectable is \ref QCP::stNone). \note \a rect must be a normalized rect (positive or zero width and height). This is especially important when using the rect of \ref QCPSelectionRect::accepted, which is not necessarily normalized. Use QRect::normalized() when passing a rect which might not be normalized. */ /*! \fn virtual double QCPPlottableInterface1D::dataMainKey(int index) const = 0 Returns the main key of the data point at the given \a index. What the main key is, is defined by the plottable's data type. See the \ref qcpdatacontainer-datatype "QCPDataContainer DataType" documentation for details about this naming convention. */ /*! \fn virtual double QCPPlottableInterface1D::dataSortKey(int index) const = 0 Returns the sort key of the data point at the given \a index. What the sort key is, is defined by the plottable's data type. See the \ref qcpdatacontainer-datatype "QCPDataContainer DataType" documentation for details about this naming convention. */ /*! \fn virtual double QCPPlottableInterface1D::dataMainValue(int index) const = 0 Returns the main value of the data point at the given \a index. What the main value is, is defined by the plottable's data type. See the \ref qcpdatacontainer-datatype "QCPDataContainer DataType" documentation for details about this naming convention. */ /*! \fn virtual QCPRange QCPPlottableInterface1D::dataValueRange(int index) const = 0 Returns the value range of the data point at the given \a index. What the value range is, is defined by the plottable's data type. See the \ref qcpdatacontainer-datatype "QCPDataContainer DataType" documentation for details about this naming convention. */ /*! \fn virtual QPointF QCPPlottableInterface1D::dataPixelPosition(int index) const = 0 Returns the pixel position on the widget surface at which the data point at the given \a index appears. Usually this corresponds to the point of \ref dataMainKey/\ref dataMainValue, in pixel coordinates. However, depending on the plottable, this might be a different apparent position than just a coord-to-pixel transform of those values. For example, \ref QCPBars apparent data values can be shifted depending on their stacking, bar grouping or configured base value. */ /*! \fn virtual bool QCPPlottableInterface1D::sortKeyIsMainKey() const = 0 Returns whether the sort key (\ref dataSortKey) is identical to the main key (\ref dataMainKey). What the sort and main keys are, is defined by the plottable's data type. See the \ref qcpdatacontainer-datatype "QCPDataContainer DataType" documentation for details about this naming convention. */ /*! \fn virtual int QCPPlottableInterface1D::findBegin(double sortKey, bool expandedRange) const = 0 Returns the index of the data point with a (sort-)key that is equal to, just below, or just above \a sortKey. If \a expandedRange is true, the data point just below \a sortKey will be considered, otherwise the one just above. This can be used in conjunction with \ref findEnd to iterate over data points within a given key range, including or excluding the bounding data points that are just beyond the specified range. If \a expandedRange is true but there are no data points below \a sortKey, 0 is returned. If the container is empty, returns 0 (in that case, \ref findEnd will also return 0, so a loop using these methods will not iterate over the index 0). \see findEnd, QCPDataContainer::findBegin */ /*! \fn virtual int QCPPlottableInterface1D::findEnd(double sortKey, bool expandedRange) const = 0 Returns the index one after the data point with a (sort-)key that is equal to, just above, or just below \a sortKey. If \a expandedRange is true, the data point just above \a sortKey will be considered, otherwise the one just below. This can be used in conjunction with \ref findBegin to iterate over data points within a given key range, including the bounding data points that are just below and above the specified range. If \a expandedRange is true but there are no data points above \a sortKey, the index just above the highest data point is returned. If the container is empty, returns 0. \see findBegin, QCPDataContainer::findEnd */ /* end documentation of pure virtual functions */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAbstractPlottable1D //////////////////////////////////////////////////////////////////////////////////////////////////// /*! \class QCPAbstractPlottable1D \brief A template base class for plottables with one-dimensional data This template class derives from \ref QCPAbstractPlottable and from the abstract interface \ref QCPPlottableInterface1D. It serves as a base class for all one-dimensional data (i.e. data with one key dimension), such as \ref QCPGraph and QCPCurve. The template parameter \a DataType is the type of the data points of this plottable (e.g. \ref QCPGraphData or \ref QCPCurveData). The main purpose of this base class is to provide the member \a mDataContainer (a shared pointer to a \ref QCPDataContainer "QCPDataContainer") and implement the according virtual methods of the \ref QCPPlottableInterface1D, such that most subclassed plottables don't need to worry about this anymore. Further, it provides a convenience method for retrieving selected/unselected data segments via \ref getDataSegments. This is useful when subclasses implement their \ref draw method and need to draw selected segments with a different pen/brush than unselected segments (also see \ref QCPSelectionDecorator). This class implements basic functionality of \ref QCPAbstractPlottable::selectTest and \ref QCPPlottableInterface1D::selectTestRect, assuming point-like data points, based on the 1D data interface. In spite of that, most plottable subclasses will want to reimplement those methods again, to provide a more accurate hit test based on their specific data visualization geometry. */ /* start documentation of inline functions */ /*! \fn QCPPlottableInterface1D *QCPAbstractPlottable1D::interface1D() Returns a \ref QCPPlottableInterface1D pointer to this plottable, providing access to its 1D interface. \seebaseclassmethod */ /* end documentation of inline functions */ /*! Forwards \a keyAxis and \a valueAxis to the \ref QCPAbstractPlottable::QCPAbstractPlottable "QCPAbstractPlottable" constructor and allocates the \a mDataContainer. */ template QCPAbstractPlottable1D::QCPAbstractPlottable1D(QCPAxis *keyAxis, QCPAxis *valueAxis) : QCPAbstractPlottable(keyAxis, valueAxis), mDataContainer(new QCPDataContainer) { } template QCPAbstractPlottable1D::~QCPAbstractPlottable1D() { } /*! \copydoc QCPPlottableInterface1D::dataCount */ template int QCPAbstractPlottable1D::dataCount() const { return mDataContainer->size(); } /*! \copydoc QCPPlottableInterface1D::dataMainKey */ template double QCPAbstractPlottable1D::dataMainKey(int index) const { if (index >= 0 && index < mDataContainer->size()) { return (mDataContainer->constBegin() + index)->mainKey(); } else { qDebug() << Q_FUNC_INFO << "Index out of bounds" << index; return 0; } } /*! \copydoc QCPPlottableInterface1D::dataSortKey */ template double QCPAbstractPlottable1D::dataSortKey(int index) const { if (index >= 0 && index < mDataContainer->size()) { return (mDataContainer->constBegin() + index)->sortKey(); } else { qDebug() << Q_FUNC_INFO << "Index out of bounds" << index; return 0; } } /*! \copydoc QCPPlottableInterface1D::dataMainValue */ template double QCPAbstractPlottable1D::dataMainValue(int index) const { if (index >= 0 && index < mDataContainer->size()) { return (mDataContainer->constBegin() + index)->mainValue(); } else { qDebug() << Q_FUNC_INFO << "Index out of bounds" << index; return 0; } } /*! \copydoc QCPPlottableInterface1D::dataValueRange */ template QCPRange QCPAbstractPlottable1D::dataValueRange(int index) const { if (index >= 0 && index < mDataContainer->size()) { return (mDataContainer->constBegin() + index)->valueRange(); } else { qDebug() << Q_FUNC_INFO << "Index out of bounds" << index; return QCPRange(0, 0); } } /*! \copydoc QCPPlottableInterface1D::dataPixelPosition */ template QPointF QCPAbstractPlottable1D::dataPixelPosition(int index) const { if (index >= 0 && index < mDataContainer->size()) { const typename QCPDataContainer::const_iterator it = mDataContainer->constBegin() + index; return coordsToPixels(it->mainKey(), it->mainValue()); } else { qDebug() << Q_FUNC_INFO << "Index out of bounds" << index; return QPointF(); } } /*! \copydoc QCPPlottableInterface1D::sortKeyIsMainKey */ template bool QCPAbstractPlottable1D::sortKeyIsMainKey() const { return DataType::sortKeyIsMainKey(); } /*! Implements a rect-selection algorithm assuming the data (accessed via the 1D data interface) is point-like. Most subclasses will want to reimplement this method again, to provide a more accurate hit test based on the true data visualization geometry. \seebaseclassmethod */ template QCPDataSelection QCPAbstractPlottable1D::selectTestRect(const QRectF &rect, bool onlySelectable) const { QCPDataSelection result; if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty()) return result; if (!mKeyAxis || !mValueAxis) return result; // convert rect given in pixels to ranges given in plot coordinates: double key1, value1, key2, value2; pixelsToCoords(rect.topLeft(), key1, value1); pixelsToCoords(rect.bottomRight(), key2, value2); QCPRange keyRange(key1, key2); // QCPRange normalizes internally so we don't have to care about whether key1 < key2 QCPRange valueRange(value1, value2); typename QCPDataContainer::const_iterator begin = mDataContainer->constBegin(); typename QCPDataContainer::const_iterator end = mDataContainer->constEnd(); if (DataType:: sortKeyIsMainKey()) // we can assume that data is sorted by main key, so can reduce the searched key interval: { begin = mDataContainer->findBegin(keyRange.lower, false); end = mDataContainer->findEnd(keyRange.upper, false); } if (begin == end) return result; int currentSegmentBegin = -1; // -1 means we're currently not in a segment that's contained in rect for (typename QCPDataContainer::const_iterator it = begin; it != end; ++it) { if (currentSegmentBegin == -1) { if (valueRange.contains(it->mainValue()) && keyRange.contains(it->mainKey())) // start segment currentSegmentBegin = it - mDataContainer->constBegin(); } else if (!valueRange.contains(it->mainValue()) || !keyRange.contains(it->mainKey())) // segment just ended { result.addDataRange(QCPDataRange(currentSegmentBegin, it - mDataContainer->constBegin()), false); currentSegmentBegin = -1; } } // process potential last segment: if (currentSegmentBegin != -1) result.addDataRange(QCPDataRange(currentSegmentBegin, end - mDataContainer->constBegin()), false); result.simplify(); return result; } /*! \copydoc QCPPlottableInterface1D::findBegin */ template int QCPAbstractPlottable1D::findBegin(double sortKey, bool expandedRange) const { return mDataContainer->findBegin(sortKey, expandedRange) - mDataContainer->constBegin(); } /*! \copydoc QCPPlottableInterface1D::findEnd */ template int QCPAbstractPlottable1D::findEnd(double sortKey, bool expandedRange) const { return mDataContainer->findEnd(sortKey, expandedRange) - mDataContainer->constBegin(); } /*! Implements a point-selection algorithm assuming the data (accessed via the 1D data interface) is point-like. Most subclasses will want to reimplement this method again, to provide a more accurate hit test based on the true data visualization geometry. \seebaseclassmethod */ template double QCPAbstractPlottable1D::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const { if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty()) return -1; if (!mKeyAxis || !mValueAxis) return -1; QCPDataSelection selectionResult; double minDistSqr = std::numeric_limits::max(); int minDistIndex = mDataContainer->size(); typename QCPDataContainer::const_iterator begin = mDataContainer->constBegin(); typename QCPDataContainer::const_iterator end = mDataContainer->constEnd(); if (DataType:: sortKeyIsMainKey()) // we can assume that data is sorted by main key, so can reduce the searched key interval: { // determine which key range comes into question, taking selection tolerance around pos into account: double posKeyMin, posKeyMax, dummy; pixelsToCoords(pos - QPointF(mParentPlot->selectionTolerance(), mParentPlot->selectionTolerance()), posKeyMin, dummy); pixelsToCoords(pos + QPointF(mParentPlot->selectionTolerance(), mParentPlot->selectionTolerance()), posKeyMax, dummy); if (posKeyMin > posKeyMax) qSwap(posKeyMin, posKeyMax); begin = mDataContainer->findBegin(posKeyMin, true); end = mDataContainer->findEnd(posKeyMax, true); } if (begin == end) return -1; QCPRange keyRange(mKeyAxis->range()); QCPRange valueRange(mValueAxis->range()); for (typename QCPDataContainer::const_iterator it = begin; it != end; ++it) { const double mainKey = it->mainKey(); const double mainValue = it->mainValue(); if (keyRange.contains(mainKey) && valueRange.contains( mainValue)) // make sure data point is inside visible range, for speedup in cases where sort key isn't main key and we iterate over all points { const double currentDistSqr = QCPVector2D(coordsToPixels(mainKey, mainValue) - pos).lengthSquared(); if (currentDistSqr < minDistSqr) { minDistSqr = currentDistSqr; minDistIndex = it - mDataContainer->constBegin(); } } } if (minDistIndex != mDataContainer->size()) selectionResult.addDataRange(QCPDataRange(minDistIndex, minDistIndex + 1), false); selectionResult.simplify(); if (details) details->setValue(selectionResult); return qSqrt(minDistSqr); } /*! Splits all data into selected and unselected segments and outputs them via \a selectedSegments and \a unselectedSegments, respectively. This is useful when subclasses implement their \ref draw method and need to draw selected segments with a different pen/brush than unselected segments (also see \ref QCPSelectionDecorator). \see setSelection */ template void QCPAbstractPlottable1D::getDataSegments(QList &selectedSegments, QList &unselectedSegments) const { selectedSegments.clear(); unselectedSegments.clear(); if (mSelectable == QCP::stWhole) // stWhole selection type draws the entire plottable with selected style if mSelection isn't empty { if (selected()) selectedSegments << QCPDataRange(0, dataCount()); else unselectedSegments << QCPDataRange(0, dataCount()); } else { QCPDataSelection sel(selection()); sel.simplify(); selectedSegments = sel.dataRanges(); unselectedSegments = sel.inverse(QCPDataRange(0, dataCount())).dataRanges(); } } /*! A helper method which draws a line with the passed \a painter, according to the pixel data in \a lineData. NaN points create gaps in the line, as expected from QCustomPlot's plottables (this is the main difference to QPainter's regular drawPolyline, which handles NaNs by lagging or crashing). Further it uses a faster line drawing technique based on \ref QCPPainter::drawLine rather than \c QPainter::drawPolyline if the configured \ref QCustomPlot::setPlottingHints() and \a painter style allows. */ template void QCPAbstractPlottable1D::drawPolyline(QCPPainter *painter, const QVector &lineData) const { // if drawing solid line and not in PDF, use much faster line drawing instead of polyline: if (mParentPlot->plottingHints().testFlag(QCP::phFastPolylines) && painter->pen().style() == Qt::SolidLine && !painter->modes().testFlag(QCPPainter::pmVectorized) && !painter->modes().testFlag(QCPPainter::pmNoCaching)) { int i = 0; bool lastIsNan = false; const int lineDataSize = lineData.size(); while (i < lineDataSize && (qIsNaN(lineData.at(i).y()) || qIsNaN(lineData.at(i).x()))) // make sure first point is not NaN ++i; ++i; // because drawing works in 1 point retrospect while (i < lineDataSize) { if (!qIsNaN(lineData.at(i).y()) && !qIsNaN(lineData.at(i).x())) // NaNs create a gap in the line { if (!lastIsNan) painter->drawLine(lineData.at(i - 1), lineData.at(i)); else lastIsNan = false; } else lastIsNan = true; ++i; } } else { int segmentStart = 0; int i = 0; const int lineDataSize = lineData.size(); while (i < lineDataSize) { if (qIsNaN(lineData.at(i).y()) || qIsNaN(lineData.at(i).x()) || qIsInf(lineData.at(i) .y())) // NaNs create a gap in the line. Also filter Infs which make drawPolyline block { painter->drawPolyline(lineData.constData() + segmentStart, i - segmentStart); // i, because we don't want to include the current NaN point segmentStart = i + 1; } ++i; } // draw last segment: painter->drawPolyline(lineData.constData() + segmentStart, lineDataSize - segmentStart); } } /* end of 'src/plottable1d.cpp' */ /* end of 'src/plottable1d.h' */ /* including file 'src/colorgradient.h', size 6243 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPColorGradient { Q_GADGET public: /*! Defines the color spaces in which color interpolation between gradient stops can be performed. \see setColorInterpolation */ enum ColorInterpolation { ciRGB ///< Color channels red, green and blue are linearly interpolated , ciHSV ///< Color channels hue, saturation and value are linearly interpolated (The hue is interpolated over the shortest angle distance) }; Q_ENUM(ColorInterpolation) /*! Defines the available presets that can be loaded with \ref loadPreset. See the documentation there for an image of the presets. */ enum GradientPreset { gpGrayscale ///< Continuous lightness from black to white (suited for non-biased data representation) , gpHot ///< Continuous lightness from black over firey colors to white (suited for non-biased data representation) , gpCold ///< Continuous lightness from black over icey colors to white (suited for non-biased data representation) , gpNight ///< Continuous lightness from black over weak blueish colors to white (suited for non-biased data representation) , gpCandy ///< Blue over pink to white , gpGeography ///< Colors suitable to represent different elevations on geographical maps , gpIon ///< Half hue spectrum from black over purple to blue and finally green (creates banding illusion but allows more precise magnitude estimates) , gpThermal ///< Colors suitable for thermal imaging, ranging from dark blue over purple to orange, yellow and white , gpPolar ///< Colors suitable to emphasize polarity around the center, with blue for negative, black in the middle and red for positive values , gpSpectrum ///< An approximation of the visible light spectrum (creates banding illusion but allows more precise magnitude estimates) , gpJet ///< Hue variation similar to a spectrum, often used in numerical visualization (creates banding illusion but allows more precise magnitude estimates) , gpHues ///< Full hue cycle, with highest and lowest color red (suitable for periodic data, such as angles and phases, see \ref setPeriodic) }; Q_ENUM(GradientPreset) QCPColorGradient(); QCPColorGradient(GradientPreset preset); bool operator==(const QCPColorGradient &other) const; bool operator!=(const QCPColorGradient &other) const { return !(*this == other); } // getters: int levelCount() const { return mLevelCount; } QMap colorStops() const { return mColorStops; } ColorInterpolation colorInterpolation() const { return mColorInterpolation; } bool periodic() const { return mPeriodic; } // setters: void setLevelCount(int n); void setColorStops(const QMap &colorStops); void setColorStopAt(double position, const QColor &color); void setColorInterpolation(ColorInterpolation interpolation); void setPeriodic(bool enabled); // non-property methods: void colorize(const double *data, const QCPRange &range, QRgb *scanLine, int n, int dataIndexFactor = 1, bool logarithmic = false); void colorize(const double *data, const unsigned char *alpha, const QCPRange &range, QRgb *scanLine, int n, int dataIndexFactor = 1, bool logarithmic = false); QRgb color(double position, const QCPRange &range, bool logarithmic = false); void loadPreset(GradientPreset preset); void clearColorStops(); QCPColorGradient inverted() const; protected: // property members: int mLevelCount; QMap mColorStops; ColorInterpolation mColorInterpolation; bool mPeriodic; // non-property members: QVector mColorBuffer; // have colors premultiplied with alpha (for usage with QImage::Format_ARGB32_Premultiplied) bool mColorBufferInvalidated; // non-virtual methods: bool stopsUseAlpha() const; void updateColorBuffer(); }; Q_DECLARE_METATYPE(QCPColorGradient::ColorInterpolation) Q_DECLARE_METATYPE(QCPColorGradient::GradientPreset) /* end of 'src/colorgradient.h' */ /* including file 'src/selectiondecorator-bracket.h', size 4426 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPSelectionDecoratorBracket : public QCPSelectionDecorator { Q_GADGET public: /*! Defines which shape is drawn at the boundaries of selected data ranges. Some of the bracket styles further allow specifying a height and/or width, see \ref setBracketHeight and \ref setBracketWidth. */ enum BracketStyle { bsSquareBracket ///< A square bracket is drawn. , bsHalfEllipse ///< A half ellipse is drawn. The size of the ellipse is given by the bracket width/height properties. , bsEllipse ///< An ellipse is drawn. The size of the ellipse is given by the bracket width/height properties. , bsPlus ///< A plus is drawn. , bsUserStyle ///< Start custom bracket styles at this index when subclassing and reimplementing \ref drawBracket. }; Q_ENUM(BracketStyle) QCPSelectionDecoratorBracket(); ~QCPSelectionDecoratorBracket() override; // getters: QPen bracketPen() const { return mBracketPen; } QBrush bracketBrush() const { return mBracketBrush; } int bracketWidth() const { return mBracketWidth; } int bracketHeight() const { return mBracketHeight; } BracketStyle bracketStyle() const { return mBracketStyle; } bool tangentToData() const { return mTangentToData; } int tangentAverage() const { return mTangentAverage; } // setters: void setBracketPen(const QPen &pen); void setBracketBrush(const QBrush &brush); void setBracketWidth(int width); void setBracketHeight(int height); void setBracketStyle(BracketStyle style); void setTangentToData(bool enabled); void setTangentAverage(int pointCount); // introduced virtual methods: virtual void drawBracket(QCPPainter *painter, int direction) const; // virtual methods: void drawDecoration(QCPPainter *painter, QCPDataSelection selection) override; protected: // property members: QPen mBracketPen; QBrush mBracketBrush; int mBracketWidth; int mBracketHeight; BracketStyle mBracketStyle; bool mTangentToData; int mTangentAverage; // non-virtual methods: double getTangentAngle(const QCPPlottableInterface1D *interface1d, int dataIndex, int direction) const; QPointF getPixelCoordinates(const QCPPlottableInterface1D *interface1d, int dataIndex) const; }; Q_DECLARE_METATYPE(QCPSelectionDecoratorBracket::BracketStyle) /* end of 'src/selectiondecorator-bracket.h' */ /* including file 'src/layoutelements/layoutelement-axisrect.h', size 7528 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPAxisRect : public QCPLayoutElement { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QPixmap background READ background WRITE setBackground) Q_PROPERTY(bool backgroundScaled READ backgroundScaled WRITE setBackgroundScaled) Q_PROPERTY(Qt::AspectRatioMode backgroundScaledMode READ backgroundScaledMode WRITE setBackgroundScaledMode) Q_PROPERTY(Qt::Orientations rangeDrag READ rangeDrag WRITE setRangeDrag) Q_PROPERTY(Qt::Orientations rangeZoom READ rangeZoom WRITE setRangeZoom) /// \endcond public: explicit QCPAxisRect(QCustomPlot *parentPlot, bool setupDefaultAxes = true); ~QCPAxisRect() override; // getters: QPixmap background() const { return mBackgroundPixmap; } QBrush backgroundBrush() const { return mBackgroundBrush; } bool backgroundScaled() const { return mBackgroundScaled; } Qt::AspectRatioMode backgroundScaledMode() const { return mBackgroundScaledMode; } Qt::Orientations rangeDrag() const { return mRangeDrag; } Qt::Orientations rangeZoom() const { return mRangeZoom; } QCPAxis *rangeDragAxis(Qt::Orientation orientation); QCPAxis *rangeZoomAxis(Qt::Orientation orientation); QList rangeDragAxes(Qt::Orientation orientation); QList rangeZoomAxes(Qt::Orientation orientation); double rangeZoomFactor(Qt::Orientation orientation); // setters: void setBackground(const QPixmap &pm); void setBackground(const QPixmap &pm, bool scaled, Qt::AspectRatioMode mode = Qt::KeepAspectRatioByExpanding); void setBackground(const QBrush &brush); void setBackgroundScaled(bool scaled); void setBackgroundScaledMode(Qt::AspectRatioMode mode); void setRangeDrag(Qt::Orientations orientations); void setRangeZoom(Qt::Orientations orientations); void setRangeDragAxes(QCPAxis *horizontal, QCPAxis *vertical); void setRangeDragAxes(QList axes); void setRangeDragAxes(QList horizontal, QList vertical); void setRangeZoomAxes(QCPAxis *horizontal, QCPAxis *vertical); void setRangeZoomAxes(QList axes); void setRangeZoomAxes(QList horizontal, QList vertical); void setRangeZoomFactor(double horizontalFactor, double verticalFactor); void setRangeZoomFactor(double factor); // non-property methods: int axisCount(QCPAxis::AxisType type) const; QCPAxis *axis(QCPAxis::AxisType type, int index = 0) const; QList axes(QCPAxis::AxisTypes types) const; QList axes() const; - QCPAxis *addAxis(QCPAxis::AxisType type, QCPAxis *axis = 0); + QCPAxis *addAxis(QCPAxis::AxisType type, QCPAxis *axis = nullptr); QList addAxes(QCPAxis::AxisTypes types); bool removeAxis(QCPAxis *axis); QCPLayoutInset *insetLayout() const { return mInsetLayout; } void zoom(const QRectF &pixelRect); void zoom(const QRectF &pixelRect, const QList &affectedAxes); void setupFullAxesBox(bool connectRanges = false); QList plottables() const; QList graphs() const; QList items() const; // read-only interface imitating a QRect: int left() const { return mRect.left(); } int right() const { return mRect.right(); } int top() const { return mRect.top(); } int bottom() const { return mRect.bottom(); } int width() const { return mRect.width(); } int height() const { return mRect.height(); } QSize size() const { return mRect.size(); } QPoint topLeft() const { return mRect.topLeft(); } QPoint topRight() const { return mRect.topRight(); } QPoint bottomLeft() const { return mRect.bottomLeft(); } QPoint bottomRight() const { return mRect.bottomRight(); } QPoint center() const { return mRect.center(); } // reimplemented virtual methods: void update(UpdatePhase phase) override; QList elements(bool recursive) const override; protected: // property members: QBrush mBackgroundBrush; QPixmap mBackgroundPixmap; QPixmap mScaledBackgroundPixmap; bool mBackgroundScaled; Qt::AspectRatioMode mBackgroundScaledMode; QCPLayoutInset *mInsetLayout; Qt::Orientations mRangeDrag, mRangeZoom; QList> mRangeDragHorzAxis, mRangeDragVertAxis; QList> mRangeZoomHorzAxis, mRangeZoomVertAxis; double mRangeZoomFactorHorz, mRangeZoomFactorVert; // non-property members: QList mDragStartHorzRange, mDragStartVertRange; QCP::AntialiasedElements mAADragBackup, mNotAADragBackup; QPoint mDragStart; bool mDragging; QHash> mAxes; // reimplemented virtual methods: void applyDefaultAntialiasingHint(QCPPainter *painter) const override; void draw(QCPPainter *painter) override; int calculateAutoMargin(QCP::MarginSide side) override; void layoutChanged() override; // events: void mousePressEvent(QMouseEvent *event, const QVariant &details) override; void mouseMoveEvent(QMouseEvent *event, const QPointF &startPos) override; void mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) override; void wheelEvent(QWheelEvent *event) override; // non-property methods: void drawBackground(QCPPainter *painter); void updateAxesOffset(QCPAxis::AxisType type); private: Q_DISABLE_COPY(QCPAxisRect) friend class QCustomPlot; }; /* end of 'src/layoutelements/layoutelement-axisrect.h' */ /* including file 'src/layoutelements/layoutelement-legend.h', size 10392 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPAbstractLegendItem : public QCPLayoutElement { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QCPLegend *parentLegend READ parentLegend) Q_PROPERTY(QFont font READ font WRITE setFont) Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor) Q_PROPERTY(QFont selectedFont READ selectedFont WRITE setSelectedFont) Q_PROPERTY(QColor selectedTextColor READ selectedTextColor WRITE setSelectedTextColor) Q_PROPERTY(bool selectable READ selectable WRITE setSelectable NOTIFY selectionChanged) Q_PROPERTY(bool selected READ selected WRITE setSelected NOTIFY selectableChanged) /// \endcond public: explicit QCPAbstractLegendItem(QCPLegend *parent); // getters: QCPLegend *parentLegend() const { return mParentLegend; } QFont font() const { return mFont; } QColor textColor() const { return mTextColor; } QFont selectedFont() const { return mSelectedFont; } QColor selectedTextColor() const { return mSelectedTextColor; } bool selectable() const { return mSelectable; } bool selected() const { return mSelected; } // setters: void setFont(const QFont &font); void setTextColor(const QColor &color); void setSelectedFont(const QFont &font); void setSelectedTextColor(const QColor &color); Q_SLOT void setSelectable(bool selectable); Q_SLOT void setSelected(bool selected); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; signals: void selectionChanged(bool selected); void selectableChanged(bool selectable); protected: // property members: QCPLegend *mParentLegend; QFont mFont; QColor mTextColor; QFont mSelectedFont; QColor mSelectedTextColor; bool mSelectable, mSelected; // reimplemented virtual methods: QCP::Interaction selectionCategory() const override; void applyDefaultAntialiasingHint(QCPPainter *painter) const override; QRect clipRect() const override; void draw(QCPPainter *painter) override = 0; // events: void selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) override; void deselectEvent(bool *selectionStateChanged) override; private: Q_DISABLE_COPY(QCPAbstractLegendItem) friend class QCPLegend; }; class QCP_LIB_DECL QCPPlottableLegendItem : public QCPAbstractLegendItem { Q_OBJECT public: QCPPlottableLegendItem(QCPLegend *parent, QCPAbstractPlottable *plottable); // getters: QCPAbstractPlottable *plottable() { return mPlottable; } protected: // property members: QCPAbstractPlottable *mPlottable; // reimplemented virtual methods: void draw(QCPPainter *painter) override; QSize minimumSizeHint() const override; // non-virtual methods: QPen getIconBorderPen() const; QColor getTextColor() const; QFont getFont() const; }; class QCP_LIB_DECL QCPLegend : public QCPLayoutGrid { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QPen borderPen READ borderPen WRITE setBorderPen) Q_PROPERTY(QBrush brush READ brush WRITE setBrush) Q_PROPERTY(QFont font READ font WRITE setFont) Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor) Q_PROPERTY(QSize iconSize READ iconSize WRITE setIconSize) Q_PROPERTY(int iconTextPadding READ iconTextPadding WRITE setIconTextPadding) Q_PROPERTY(QPen iconBorderPen READ iconBorderPen WRITE setIconBorderPen) Q_PROPERTY(SelectableParts selectableParts READ selectableParts WRITE setSelectableParts NOTIFY selectionChanged) Q_PROPERTY(SelectableParts selectedParts READ selectedParts WRITE setSelectedParts NOTIFY selectableChanged) Q_PROPERTY(QPen selectedBorderPen READ selectedBorderPen WRITE setSelectedBorderPen) Q_PROPERTY(QPen selectedIconBorderPen READ selectedIconBorderPen WRITE setSelectedIconBorderPen) Q_PROPERTY(QBrush selectedBrush READ selectedBrush WRITE setSelectedBrush) Q_PROPERTY(QFont selectedFont READ selectedFont WRITE setSelectedFont) Q_PROPERTY(QColor selectedTextColor READ selectedTextColor WRITE setSelectedTextColor) /// \endcond public: /*! Defines the selectable parts of a legend \see setSelectedParts, setSelectableParts */ enum SelectablePart { spNone = 0x000 ///< 0x000 None , spLegendBox = 0x001 ///< 0x001 The legend box (frame) , spItems = 0x002 ///< 0x002 Legend items individually (see \ref selectedItems) }; Q_ENUM(SelectablePart) Q_FLAGS(SelectableParts) Q_DECLARE_FLAGS(SelectableParts, SelectablePart) explicit QCPLegend(); ~QCPLegend() override; // getters: QPen borderPen() const { return mBorderPen; } QBrush brush() const { return mBrush; } QFont font() const { return mFont; } QColor textColor() const { return mTextColor; } QSize iconSize() const { return mIconSize; } int iconTextPadding() const { return mIconTextPadding; } QPen iconBorderPen() const { return mIconBorderPen; } SelectableParts selectableParts() const { return mSelectableParts; } SelectableParts selectedParts() const; QPen selectedBorderPen() const { return mSelectedBorderPen; } QPen selectedIconBorderPen() const { return mSelectedIconBorderPen; } QBrush selectedBrush() const { return mSelectedBrush; } QFont selectedFont() const { return mSelectedFont; } QColor selectedTextColor() const { return mSelectedTextColor; } // setters: void setBorderPen(const QPen &pen); void setBrush(const QBrush &brush); void setFont(const QFont &font); void setTextColor(const QColor &color); void setIconSize(const QSize &size); void setIconSize(int width, int height); void setIconTextPadding(int padding); void setIconBorderPen(const QPen &pen); Q_SLOT void setSelectableParts(const SelectableParts &selectableParts); Q_SLOT void setSelectedParts(const SelectableParts &selectedParts); void setSelectedBorderPen(const QPen &pen); void setSelectedIconBorderPen(const QPen &pen); void setSelectedBrush(const QBrush &brush); void setSelectedFont(const QFont &font); void setSelectedTextColor(const QColor &color); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; // non-virtual methods: QCPAbstractLegendItem *item(int index) const; QCPPlottableLegendItem *itemWithPlottable(const QCPAbstractPlottable *plottable) const; int itemCount() const; bool hasItem(QCPAbstractLegendItem *item) const; bool hasItemWithPlottable(const QCPAbstractPlottable *plottable) const; bool addItem(QCPAbstractLegendItem *item); bool removeItem(int index); bool removeItem(QCPAbstractLegendItem *item); void clearItems(); QList selectedItems() const; signals: void selectionChanged(QCPLegend::SelectableParts parts); void selectableChanged(QCPLegend::SelectableParts parts); protected: // property members: QPen mBorderPen, mIconBorderPen; QBrush mBrush; QFont mFont; QColor mTextColor; QSize mIconSize; int mIconTextPadding; SelectableParts mSelectedParts, mSelectableParts; QPen mSelectedBorderPen, mSelectedIconBorderPen; QBrush mSelectedBrush; QFont mSelectedFont; QColor mSelectedTextColor; // reimplemented virtual methods: void parentPlotInitialized(QCustomPlot *parentPlot) override; QCP::Interaction selectionCategory() const override; void applyDefaultAntialiasingHint(QCPPainter *painter) const override; void draw(QCPPainter *painter) override; // events: void selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) override; void deselectEvent(bool *selectionStateChanged) override; // non-virtual methods: QPen getBorderPen() const; QBrush getBrush() const; private: Q_DISABLE_COPY(QCPLegend) friend class QCustomPlot; friend class QCPAbstractLegendItem; }; Q_DECLARE_OPERATORS_FOR_FLAGS(QCPLegend::SelectableParts) Q_DECLARE_METATYPE(QCPLegend::SelectablePart) /* end of 'src/layoutelements/layoutelement-legend.h' */ /* including file 'src/layoutelements/layoutelement-textelement.h', size 5343 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPTextElement : public QCPLayoutElement { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QString text READ text WRITE setText) Q_PROPERTY(QFont font READ font WRITE setFont) Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor) Q_PROPERTY(QFont selectedFont READ selectedFont WRITE setSelectedFont) Q_PROPERTY(QColor selectedTextColor READ selectedTextColor WRITE setSelectedTextColor) Q_PROPERTY(bool selectable READ selectable WRITE setSelectable NOTIFY selectableChanged) Q_PROPERTY(bool selected READ selected WRITE setSelected NOTIFY selectionChanged) /// \endcond public: explicit QCPTextElement(QCustomPlot *parentPlot); QCPTextElement(QCustomPlot *parentPlot, const QString &text); QCPTextElement(QCustomPlot *parentPlot, const QString &text, double pointSize); QCPTextElement(QCustomPlot *parentPlot, const QString &text, const QString &fontFamily, double pointSize); QCPTextElement(QCustomPlot *parentPlot, const QString &text, const QFont &font); // getters: QString text() const { return mText; } int textFlags() const { return mTextFlags; } QFont font() const { return mFont; } QColor textColor() const { return mTextColor; } QFont selectedFont() const { return mSelectedFont; } QColor selectedTextColor() const { return mSelectedTextColor; } bool selectable() const { return mSelectable; } bool selected() const { return mSelected; } // setters: void setText(const QString &text); void setTextFlags(int flags); void setFont(const QFont &font); void setTextColor(const QColor &color); void setSelectedFont(const QFont &font); void setSelectedTextColor(const QColor &color); Q_SLOT void setSelectable(bool selectable); Q_SLOT void setSelected(bool selected); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; void mousePressEvent(QMouseEvent *event, const QVariant &details) override; void mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) override; void mouseDoubleClickEvent(QMouseEvent *event, const QVariant &details) override; signals: void selectionChanged(bool selected); void selectableChanged(bool selectable); void clicked(QMouseEvent *event); void doubleClicked(QMouseEvent *event); protected: // property members: QString mText; int mTextFlags; QFont mFont; QColor mTextColor; QFont mSelectedFont; QColor mSelectedTextColor; QRect mTextBoundingRect; bool mSelectable, mSelected; // reimplemented virtual methods: void applyDefaultAntialiasingHint(QCPPainter *painter) const override; void draw(QCPPainter *painter) override; QSize minimumSizeHint() const override; QSize maximumSizeHint() const override; // events: void selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) override; void deselectEvent(bool *selectionStateChanged) override; // non-virtual methods: QFont mainFont() const; QColor mainTextColor() const; private: Q_DISABLE_COPY(QCPTextElement) }; /* end of 'src/layoutelements/layoutelement-textelement.h' */ /* including file 'src/layoutelements/layoutelement-colorscale.h', size 5907 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCPColorScaleAxisRectPrivate : public QCPAxisRect { Q_OBJECT public: explicit QCPColorScaleAxisRectPrivate(QCPColorScale *parentColorScale); protected: QCPColorScale *mParentColorScale; QImage mGradientImage; bool mGradientImageInvalidated; // re-using some methods of QCPAxisRect to make them available to friend class QCPColorScale using QCPAxisRect::calculateAutoMargin; using QCPAxisRect::mousePressEvent; using QCPAxisRect::mouseMoveEvent; using QCPAxisRect::mouseReleaseEvent; using QCPAxisRect::wheelEvent; using QCPAxisRect::update; void draw(QCPPainter *painter) override; void updateGradientImage(); Q_SLOT void axisSelectionChanged(QCPAxis::SelectableParts selectedParts); Q_SLOT void axisSelectableChanged(QCPAxis::SelectableParts selectableParts); friend class QCPColorScale; }; class QCP_LIB_DECL QCPColorScale : public QCPLayoutElement { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QCPAxis::AxisType type READ type WRITE setType) Q_PROPERTY(QCPRange dataRange READ dataRange WRITE setDataRange NOTIFY dataRangeChanged) Q_PROPERTY(QCPAxis::ScaleType dataScaleType READ dataScaleType WRITE setDataScaleType NOTIFY dataScaleTypeChanged) Q_PROPERTY(QCPColorGradient gradient READ gradient WRITE setGradient NOTIFY gradientChanged) Q_PROPERTY(QString label READ label WRITE setLabel) Q_PROPERTY(int barWidth READ barWidth WRITE setBarWidth) Q_PROPERTY(bool rangeDrag READ rangeDrag WRITE setRangeDrag) Q_PROPERTY(bool rangeZoom READ rangeZoom WRITE setRangeZoom) /// \endcond public: explicit QCPColorScale(QCustomPlot *parentPlot); ~QCPColorScale() override; // getters: QCPAxis *axis() const { return mColorAxis.data(); } QCPAxis::AxisType type() const { return mType; } QCPRange dataRange() const { return mDataRange; } QCPAxis::ScaleType dataScaleType() const { return mDataScaleType; } QCPColorGradient gradient() const { return mGradient; } QString label() const; int barWidth() const { return mBarWidth; } bool rangeDrag() const; bool rangeZoom() const; // setters: void setType(QCPAxis::AxisType type); Q_SLOT void setDataRange(const QCPRange &dataRange); Q_SLOT void setDataScaleType(QCPAxis::ScaleType scaleType); Q_SLOT void setGradient(const QCPColorGradient &gradient); void setLabel(const QString &str); void setBarWidth(int width); void setRangeDrag(bool enabled); void setRangeZoom(bool enabled); // non-property methods: QList colorMaps() const; void rescaleDataRange(bool onlyVisibleMaps); // reimplemented virtual methods: void update(UpdatePhase phase) override; signals: void dataRangeChanged(const QCPRange &newRange); void dataScaleTypeChanged(QCPAxis::ScaleType scaleType); void gradientChanged(const QCPColorGradient &newGradient); protected: // property members: QCPAxis::AxisType mType; QCPRange mDataRange; QCPAxis::ScaleType mDataScaleType; QCPColorGradient mGradient; int mBarWidth; // non-property members: QPointer mAxisRect; QPointer mColorAxis; // reimplemented virtual methods: void applyDefaultAntialiasingHint(QCPPainter *painter) const override; // events: void mousePressEvent(QMouseEvent *event, const QVariant &details) override; void mouseMoveEvent(QMouseEvent *event, const QPointF &startPos) override; void mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) override; void wheelEvent(QWheelEvent *event) override; private: Q_DISABLE_COPY(QCPColorScale) friend class QCPColorScaleAxisRectPrivate; }; /* end of 'src/layoutelements/layoutelement-colorscale.h' */ /* including file 'src/plottables/plottable-graph.h', size 8826 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPGraphData { public: QCPGraphData(); QCPGraphData(double key, double value); inline double sortKey() const { return key; } inline static QCPGraphData fromSortKey(double sortKey) { return QCPGraphData(sortKey, 0); } inline static bool sortKeyIsMainKey() { return true; } inline double mainKey() const { return key; } inline double mainValue() const { return value; } inline QCPRange valueRange() const { return QCPRange(value, value); } double key, value; }; Q_DECLARE_TYPEINFO(QCPGraphData, Q_PRIMITIVE_TYPE); /*! \typedef QCPGraphDataContainer Container for storing \ref QCPGraphData points. The data is stored sorted by \a key. This template instantiation is the container in which QCPGraph holds its data. For details about the generic container, see the documentation of the class template \ref QCPDataContainer. \see QCPGraphData, QCPGraph::setData */ typedef QCPDataContainer QCPGraphDataContainer; class QCP_LIB_DECL QCPGraph : public QCPAbstractPlottable1D { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(LineStyle lineStyle READ lineStyle WRITE setLineStyle) Q_PROPERTY(QCPScatterStyle scatterStyle READ scatterStyle WRITE setScatterStyle) Q_PROPERTY(int scatterSkip READ scatterSkip WRITE setScatterSkip) Q_PROPERTY(QCPGraph *channelFillGraph READ channelFillGraph WRITE setChannelFillGraph) Q_PROPERTY(bool adaptiveSampling READ adaptiveSampling WRITE setAdaptiveSampling) /// \endcond public: /*! Defines how the graph's line is represented visually in the plot. The line is drawn with the current pen of the graph (\ref setPen). \see setLineStyle */ enum LineStyle { lsNone ///< data points are not connected with any lines (e.g. data only represented ///< with symbols according to the scatter style, see \ref setScatterStyle) , lsLine ///< data points are connected by a straight line , lsStepLeft ///< line is drawn as steps where the step height is the value of the left data point , lsStepRight ///< line is drawn as steps where the step height is the value of the right data point , lsStepCenter ///< line is drawn as steps where the step is in between two data points , lsImpulse ///< each data point is represented by a line parallel to the value axis, which reaches from the data point to the zero-value-line }; Q_ENUM(LineStyle) explicit QCPGraph(QCPAxis *keyAxis, QCPAxis *valueAxis); ~QCPGraph() override; // getters: QSharedPointer data() const { return mDataContainer; } LineStyle lineStyle() const { return mLineStyle; } QCPScatterStyle scatterStyle() const { return mScatterStyle; } int scatterSkip() const { return mScatterSkip; } QCPGraph *channelFillGraph() const { return mChannelFillGraph.data(); } bool adaptiveSampling() const { return mAdaptiveSampling; } // setters: void setData(QSharedPointer data); void setData(const QVector &keys, const QVector &values, bool alreadySorted = false); void setLineStyle(LineStyle ls); void setScatterStyle(const QCPScatterStyle &style); void setScatterSkip(int skip); void setChannelFillGraph(QCPGraph *targetGraph); void setAdaptiveSampling(bool enabled); // non-property methods: void addData(const QVector &keys, const QVector &values, bool alreadySorted = false); void addData(double key, double value); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain = QCP::sdBoth) const override; QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain = QCP::sdBoth, const QCPRange &inKeyRange = QCPRange()) const override; protected: // property members: LineStyle mLineStyle; QCPScatterStyle mScatterStyle; int mScatterSkip; QPointer mChannelFillGraph; bool mAdaptiveSampling; // reimplemented virtual methods: void draw(QCPPainter *painter) override; void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const override; // introduced virtual methods: virtual void drawFill(QCPPainter *painter, QVector *lines) const; virtual void drawScatterPlot(QCPPainter *painter, const QVector &scatters, const QCPScatterStyle &style) const; virtual void drawLinePlot(QCPPainter *painter, const QVector &lines) const; virtual void drawImpulsePlot(QCPPainter *painter, const QVector &lines) const; virtual void getOptimizedLineData(QVector *lineData, const QCPGraphDataContainer::const_iterator &begin, const QCPGraphDataContainer::const_iterator &end) const; virtual void getOptimizedScatterData(QVector *scatterData, QCPGraphDataContainer::const_iterator begin, QCPGraphDataContainer::const_iterator end) const; // non-virtual methods: void getVisibleDataBounds(QCPGraphDataContainer::const_iterator &begin, QCPGraphDataContainer::const_iterator &end, const QCPDataRange &rangeRestriction) const; void getLines(QVector *lines, const QCPDataRange &dataRange) const; void getScatters(QVector *scatters, const QCPDataRange &dataRange) const; QVector dataToLines(const QVector &data) const; QVector dataToStepLeftLines(const QVector &data) const; QVector dataToStepRightLines(const QVector &data) const; QVector dataToStepCenterLines(const QVector &data) const; QVector dataToImpulseLines(const QVector &data) const; void addFillBasePoints(QVector *lines) const; void removeFillBasePoints(QVector *lines) const; QPointF lowerFillBasePoint(double lowerKey) const; QPointF upperFillBasePoint(double upperKey) const; const QPolygonF getChannelFillPolygon(const QVector *lines) const; int findIndexBelowX(const QVector *data, double x) const; int findIndexAboveX(const QVector *data, double x) const; int findIndexBelowY(const QVector *data, double y) const; int findIndexAboveY(const QVector *data, double y) const; double pointDistance(const QPointF &pixelPoint, QCPGraphDataContainer::const_iterator &closestData) const; friend class QCustomPlot; friend class QCPLegend; }; Q_DECLARE_METATYPE(QCPGraph::LineStyle) /* end of 'src/plottables/plottable-graph.h' */ /* including file 'src/plottables/plottable-curve.h', size 7409 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPCurveData { public: QCPCurveData(); QCPCurveData(double t, double key, double value); inline double sortKey() const { return t; } inline static QCPCurveData fromSortKey(double sortKey) { return QCPCurveData(sortKey, 0, 0); } inline static bool sortKeyIsMainKey() { return false; } inline double mainKey() const { return key; } inline double mainValue() const { return value; } inline QCPRange valueRange() const { return QCPRange(value, value); } double t, key, value; }; Q_DECLARE_TYPEINFO(QCPCurveData, Q_PRIMITIVE_TYPE); /*! \typedef QCPCurveDataContainer Container for storing \ref QCPCurveData points. The data is stored sorted by \a t, so the \a sortKey() (returning \a t) is different from \a mainKey() (returning \a key). This template instantiation is the container in which QCPCurve holds its data. For details about the generic container, see the documentation of the class template \ref QCPDataContainer. \see QCPCurveData, QCPCurve::setData */ typedef QCPDataContainer QCPCurveDataContainer; class QCP_LIB_DECL QCPCurve : public QCPAbstractPlottable1D { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QCPScatterStyle scatterStyle READ scatterStyle WRITE setScatterStyle) Q_PROPERTY(int scatterSkip READ scatterSkip WRITE setScatterSkip) Q_PROPERTY(LineStyle lineStyle READ lineStyle WRITE setLineStyle) /// \endcond public: /*! Defines how the curve's line is represented visually in the plot. The line is drawn with the current pen of the curve (\ref setPen). \see setLineStyle */ enum LineStyle { lsNone ///< No line is drawn between data points (e.g. only scatters) , lsLine ///< Data points are connected with a straight line }; Q_ENUM(LineStyle) explicit QCPCurve(QCPAxis *keyAxis, QCPAxis *valueAxis); ~QCPCurve() override; // getters: QSharedPointer data() const { return mDataContainer; } QCPScatterStyle scatterStyle() const { return mScatterStyle; } int scatterSkip() const { return mScatterSkip; } LineStyle lineStyle() const { return mLineStyle; } // setters: void setData(QSharedPointer data); void setData(const QVector &t, const QVector &keys, const QVector &values, bool alreadySorted = false); void setData(const QVector &keys, const QVector &values); void setScatterStyle(const QCPScatterStyle &style); void setScatterSkip(int skip); void setLineStyle(LineStyle style); // non-property methods: void addData(const QVector &t, const QVector &keys, const QVector &values, bool alreadySorted = false); void addData(const QVector &keys, const QVector &values); void addData(double t, double key, double value); void addData(double key, double value); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain = QCP::sdBoth) const override; QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain = QCP::sdBoth, const QCPRange &inKeyRange = QCPRange()) const override; protected: // property members: QCPScatterStyle mScatterStyle; int mScatterSkip; LineStyle mLineStyle; // reimplemented virtual methods: void draw(QCPPainter *painter) override; void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const override; // introduced virtual methods: virtual void drawCurveLine(QCPPainter *painter, const QVector &lines) const; virtual void drawScatterPlot(QCPPainter *painter, const QVector &points, const QCPScatterStyle &style) const; // non-virtual methods: void getCurveLines(QVector *lines, const QCPDataRange &dataRange, double penWidth) const; void getScatters(QVector *scatters, const QCPDataRange &dataRange, double scatterWidth) const; int getRegion(double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const; QPointF getOptimizedPoint(int prevRegion, double prevKey, double prevValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const; QVector getOptimizedCornerPoints(int prevRegion, int currentRegion, double prevKey, double prevValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const; bool mayTraverse(int prevRegion, int currentRegion) const; bool getTraverse(double prevKey, double prevValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin, QPointF &crossA, QPointF &crossB) const; void getTraverseCornerPoints(int prevRegion, int currentRegion, double keyMin, double valueMax, double keyMax, double valueMin, QVector &beforeTraverse, QVector &afterTraverse) const; double pointDistance(const QPointF &pixelPoint, QCPCurveDataContainer::const_iterator &closestData) const; friend class QCustomPlot; friend class QCPLegend; }; Q_DECLARE_METATYPE(QCPCurve::LineStyle) /* end of 'src/plottables/plottable-curve.h' */ /* including file 'src/plottables/plottable-bars.h', size 8924 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPBarsGroup : public QObject { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(SpacingType spacingType READ spacingType WRITE setSpacingType) Q_PROPERTY(double spacing READ spacing WRITE setSpacing) /// \endcond public: /*! Defines the ways the spacing between bars in the group can be specified. Thus it defines what the number passed to \ref setSpacing actually means. \see setSpacingType, setSpacing */ enum SpacingType { stAbsolute ///< Bar spacing is in absolute pixels , stAxisRectRatio ///< Bar spacing is given by a fraction of the axis rect size , stPlotCoords ///< Bar spacing is in key coordinates and thus scales with the key axis range }; Q_ENUM(SpacingType) QCPBarsGroup(QCustomPlot *parentPlot); ~QCPBarsGroup() override; // getters: SpacingType spacingType() const { return mSpacingType; } double spacing() const { return mSpacing; } // setters: void setSpacingType(SpacingType spacingType); void setSpacing(double spacing); // non-virtual methods: QList bars() const { return mBars; } QCPBars *bars(int index) const; int size() const { return mBars.size(); } bool isEmpty() const { return mBars.isEmpty(); } void clear(); bool contains(QCPBars *bars) const { return mBars.contains(bars); } void append(QCPBars *bars); void insert(int i, QCPBars *bars); void remove(QCPBars *bars); protected: // non-property members: QCustomPlot *mParentPlot; SpacingType mSpacingType; double mSpacing; QList mBars; // non-virtual methods: void registerBars(QCPBars *bars); void unregisterBars(QCPBars *bars); // virtual methods: double keyPixelOffset(const QCPBars *bars, double keyCoord); double getPixelSpacing(const QCPBars *bars, double keyCoord); private: Q_DISABLE_COPY(QCPBarsGroup) friend class QCPBars; }; Q_DECLARE_METATYPE(QCPBarsGroup::SpacingType) class QCP_LIB_DECL QCPBarsData { public: QCPBarsData(); QCPBarsData(double key, double value); inline double sortKey() const { return key; } inline static QCPBarsData fromSortKey(double sortKey) { return QCPBarsData(sortKey, 0); } inline static bool sortKeyIsMainKey() { return true; } inline double mainKey() const { return key; } inline double mainValue() const { return value; } inline QCPRange valueRange() const { return QCPRange( value, value); // note that bar base value isn't held in each QCPBarsData and thus can't/shouldn't be returned here } double key, value; }; Q_DECLARE_TYPEINFO(QCPBarsData, Q_PRIMITIVE_TYPE); /*! \typedef QCPBarsDataContainer Container for storing \ref QCPBarsData points. The data is stored sorted by \a key. This template instantiation is the container in which QCPBars holds its data. For details about the generic container, see the documentation of the class template \ref QCPDataContainer. \see QCPBarsData, QCPBars::setData */ typedef QCPDataContainer QCPBarsDataContainer; class QCP_LIB_DECL QCPBars : public QCPAbstractPlottable1D { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(double width READ width WRITE setWidth) Q_PROPERTY(WidthType widthType READ widthType WRITE setWidthType) Q_PROPERTY(QCPBarsGroup *barsGroup READ barsGroup WRITE setBarsGroup) Q_PROPERTY(double baseValue READ baseValue WRITE setBaseValue) Q_PROPERTY(double stackingGap READ stackingGap WRITE setStackingGap) Q_PROPERTY(QCPBars *barBelow READ barBelow) Q_PROPERTY(QCPBars *barAbove READ barAbove) /// \endcond public: /*! Defines the ways the width of the bar can be specified. Thus it defines what the number passed to \ref setWidth actually means. \see setWidthType, setWidth */ enum WidthType { wtAbsolute ///< Bar width is in absolute pixels , wtAxisRectRatio ///< Bar width is given by a fraction of the axis rect size , wtPlotCoords ///< Bar width is in key coordinates and thus scales with the key axis range }; Q_ENUM(WidthType) explicit QCPBars(QCPAxis *keyAxis, QCPAxis *valueAxis); ~QCPBars() override; // getters: double width() const { return mWidth; } WidthType widthType() const { return mWidthType; } QCPBarsGroup *barsGroup() const { return mBarsGroup; } double baseValue() const { return mBaseValue; } double stackingGap() const { return mStackingGap; } QCPBars *barBelow() const { return mBarBelow.data(); } QCPBars *barAbove() const { return mBarAbove.data(); } QSharedPointer data() const { return mDataContainer; } // setters: void setData(QSharedPointer data); void setData(const QVector &keys, const QVector &values, bool alreadySorted = false); void setWidth(double width); void setWidthType(WidthType widthType); void setBarsGroup(QCPBarsGroup *barsGroup); void setBaseValue(double baseValue); void setStackingGap(double pixels); // non-property methods: void addData(const QVector &keys, const QVector &values, bool alreadySorted = false); void addData(double key, double value); void moveBelow(QCPBars *bars); void moveAbove(QCPBars *bars); // reimplemented virtual methods: QCPDataSelection selectTestRect(const QRectF &rect, bool onlySelectable) const override; - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain = QCP::sdBoth) const override; QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain = QCP::sdBoth, const QCPRange &inKeyRange = QCPRange()) const override; QPointF dataPixelPosition(int index) const override; protected: // property members: double mWidth; WidthType mWidthType; QCPBarsGroup *mBarsGroup; double mBaseValue; double mStackingGap; QPointer mBarBelow, mBarAbove; // reimplemented virtual methods: void draw(QCPPainter *painter) override; void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const override; // non-virtual methods: void getVisibleDataBounds(QCPBarsDataContainer::const_iterator &begin, QCPBarsDataContainer::const_iterator &end) const; QRectF getBarRect(double key, double value) const; void getPixelWidth(double key, double &lower, double &upper) const; double getStackedBaseValue(double key, bool positive) const; static void connectBars(QCPBars *lower, QCPBars *upper); friend class QCustomPlot; friend class QCPLegend; friend class QCPBarsGroup; }; Q_DECLARE_METATYPE(QCPBars::WidthType) /* end of 'src/plottables/plottable-bars.h' */ /* including file 'src/plottables/plottable-statisticalbox.h', size 7516 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPStatisticalBoxData { public: QCPStatisticalBoxData(); QCPStatisticalBoxData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum, const QVector &outliers = QVector()); inline double sortKey() const { return key; } inline static QCPStatisticalBoxData fromSortKey(double sortKey) { return QCPStatisticalBoxData(sortKey, 0, 0, 0, 0, 0); } inline static bool sortKeyIsMainKey() { return true; } inline double mainKey() const { return key; } inline double mainValue() const { return median; } inline QCPRange valueRange() const { QCPRange result(minimum, maximum); for (QVector::const_iterator it = outliers.constBegin(); it != outliers.constEnd(); ++it) result.expand(*it); return result; } double key, minimum, lowerQuartile, median, upperQuartile, maximum; QVector outliers; }; Q_DECLARE_TYPEINFO(QCPStatisticalBoxData, Q_MOVABLE_TYPE); /*! \typedef QCPStatisticalBoxDataContainer Container for storing \ref QCPStatisticalBoxData points. The data is stored sorted by \a key. This template instantiation is the container in which QCPStatisticalBox holds its data. For details about the generic container, see the documentation of the class template \ref QCPDataContainer. \see QCPStatisticalBoxData, QCPStatisticalBox::setData */ typedef QCPDataContainer QCPStatisticalBoxDataContainer; class QCP_LIB_DECL QCPStatisticalBox : public QCPAbstractPlottable1D { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(double width READ width WRITE setWidth) Q_PROPERTY(double whiskerWidth READ whiskerWidth WRITE setWhiskerWidth) Q_PROPERTY(QPen whiskerPen READ whiskerPen WRITE setWhiskerPen) Q_PROPERTY(QPen whiskerBarPen READ whiskerBarPen WRITE setWhiskerBarPen) Q_PROPERTY(bool whiskerAntialiased READ whiskerAntialiased WRITE setWhiskerAntialiased) Q_PROPERTY(QPen medianPen READ medianPen WRITE setMedianPen) Q_PROPERTY(QCPScatterStyle outlierStyle READ outlierStyle WRITE setOutlierStyle) /// \endcond public: explicit QCPStatisticalBox(QCPAxis *keyAxis, QCPAxis *valueAxis); // getters: QSharedPointer data() const { return mDataContainer; } double width() const { return mWidth; } double whiskerWidth() const { return mWhiskerWidth; } QPen whiskerPen() const { return mWhiskerPen; } QPen whiskerBarPen() const { return mWhiskerBarPen; } bool whiskerAntialiased() const { return mWhiskerAntialiased; } QPen medianPen() const { return mMedianPen; } QCPScatterStyle outlierStyle() const { return mOutlierStyle; } // setters: void setData(QSharedPointer data); void setData(const QVector &keys, const QVector &minimum, const QVector &lowerQuartile, const QVector &median, const QVector &upperQuartile, const QVector &maximum, bool alreadySorted = false); void setWidth(double width); void setWhiskerWidth(double width); void setWhiskerPen(const QPen &pen); void setWhiskerBarPen(const QPen &pen); void setWhiskerAntialiased(bool enabled); void setMedianPen(const QPen &pen); void setOutlierStyle(const QCPScatterStyle &style); // non-property methods: void addData(const QVector &keys, const QVector &minimum, const QVector &lowerQuartile, const QVector &median, const QVector &upperQuartile, const QVector &maximum, bool alreadySorted = false); void addData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum, const QVector &outliers = QVector()); // reimplemented virtual methods: QCPDataSelection selectTestRect(const QRectF &rect, bool onlySelectable) const override; - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain = QCP::sdBoth) const override; QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain = QCP::sdBoth, const QCPRange &inKeyRange = QCPRange()) const override; protected: // property members: double mWidth; double mWhiskerWidth; QPen mWhiskerPen, mWhiskerBarPen; bool mWhiskerAntialiased; QPen mMedianPen; QCPScatterStyle mOutlierStyle; // reimplemented virtual methods: void draw(QCPPainter *painter) override; void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const override; // introduced virtual methods: virtual void drawStatisticalBox(QCPPainter *painter, QCPStatisticalBoxDataContainer::const_iterator it, const QCPScatterStyle &outlierStyle) const; // non-virtual methods: void getVisibleDataBounds(QCPStatisticalBoxDataContainer::const_iterator &begin, QCPStatisticalBoxDataContainer::const_iterator &end) const; QRectF getQuartileBox(QCPStatisticalBoxDataContainer::const_iterator it) const; QVector getWhiskerBackboneLines(QCPStatisticalBoxDataContainer::const_iterator it) const; QVector getWhiskerBarLines(QCPStatisticalBoxDataContainer::const_iterator it) const; friend class QCustomPlot; friend class QCPLegend; }; /* end of 'src/plottables/plottable-statisticalbox.h' */ /* including file 'src/plottables/plottable-colormap.h', size 7070 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPColorMapData { public: QCPColorMapData(int keySize, int valueSize, const QCPRange &keyRange, const QCPRange &valueRange); ~QCPColorMapData(); QCPColorMapData(const QCPColorMapData &other); QCPColorMapData &operator=(const QCPColorMapData &other); // getters: int keySize() const { return mKeySize; } int valueSize() const { return mValueSize; } QCPRange keyRange() const { return mKeyRange; } QCPRange valueRange() const { return mValueRange; } QCPRange dataBounds() const { return mDataBounds; } double data(double key, double value); double cell(int keyIndex, int valueIndex); unsigned char alpha(int keyIndex, int valueIndex); // setters: void setSize(int keySize, int valueSize); void setKeySize(int keySize); void setValueSize(int valueSize); void setRange(const QCPRange &keyRange, const QCPRange &valueRange); void setKeyRange(const QCPRange &keyRange); void setValueRange(const QCPRange &valueRange); void setData(double key, double value, double z); void setCell(int keyIndex, int valueIndex, double z); void setAlpha(int keyIndex, int valueIndex, unsigned char alpha); // non-property methods: void recalculateDataBounds(); void clear(); void clearAlpha(); void fill(double z); void fillAlpha(unsigned char alpha); bool isEmpty() const { return mIsEmpty; } void coordToCell(double key, double value, int *keyIndex, int *valueIndex) const; void cellToCoord(int keyIndex, int valueIndex, double *key, double *value) const; protected: // property members: int mKeySize, mValueSize; QCPRange mKeyRange, mValueRange; bool mIsEmpty; // non-property members: double *mData; unsigned char *mAlpha; QCPRange mDataBounds; bool mDataModified; bool createAlpha(bool initializeOpaque = true); friend class QCPColorMap; }; class QCP_LIB_DECL QCPColorMap : public QCPAbstractPlottable { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QCPRange dataRange READ dataRange WRITE setDataRange NOTIFY dataRangeChanged) Q_PROPERTY(QCPAxis::ScaleType dataScaleType READ dataScaleType WRITE setDataScaleType NOTIFY dataScaleTypeChanged) Q_PROPERTY(QCPColorGradient gradient READ gradient WRITE setGradient NOTIFY gradientChanged) Q_PROPERTY(bool interpolate READ interpolate WRITE setInterpolate) Q_PROPERTY(bool tightBoundary READ tightBoundary WRITE setTightBoundary) Q_PROPERTY(QCPColorScale *colorScale READ colorScale WRITE setColorScale) /// \endcond public: explicit QCPColorMap(QCPAxis *keyAxis, QCPAxis *valueAxis); ~QCPColorMap() override; // getters: QCPColorMapData *data() const { return mMapData; } QCPRange dataRange() const { return mDataRange; } QCPAxis::ScaleType dataScaleType() const { return mDataScaleType; } bool interpolate() const { return mInterpolate; } bool tightBoundary() const { return mTightBoundary; } QCPColorGradient gradient() const { return mGradient; } QCPColorScale *colorScale() const { return mColorScale.data(); } // setters: void setData(QCPColorMapData *data, bool copy = false); Q_SLOT void setDataRange(const QCPRange &dataRange); Q_SLOT void setDataScaleType(QCPAxis::ScaleType scaleType); Q_SLOT void setGradient(const QCPColorGradient &gradient); void setInterpolate(bool enabled); void setTightBoundary(bool enabled); void setColorScale(QCPColorScale *colorScale); // non-property methods: void rescaleDataRange(bool recalculateDataBounds = false); Q_SLOT void updateLegendIcon(Qt::TransformationMode transformMode = Qt::SmoothTransformation, const QSize &thumbSize = QSize(32, 18)); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain = QCP::sdBoth) const override; QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain = QCP::sdBoth, const QCPRange &inKeyRange = QCPRange()) const override; signals: void dataRangeChanged(const QCPRange &newRange); void dataScaleTypeChanged(QCPAxis::ScaleType scaleType); void gradientChanged(const QCPColorGradient &newGradient); protected: // property members: QCPRange mDataRange; QCPAxis::ScaleType mDataScaleType; QCPColorMapData *mMapData; QCPColorGradient mGradient; bool mInterpolate; bool mTightBoundary; QPointer mColorScale; // non-property members: QImage mMapImage, mUndersampledMapImage; QPixmap mLegendIcon; bool mMapImageInvalidated; // introduced virtual methods: virtual void updateMapImage(); // reimplemented virtual methods: void draw(QCPPainter *painter) override; void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const override; friend class QCustomPlot; friend class QCPLegend; }; /* end of 'src/plottables/plottable-colormap.h' */ /* including file 'src/plottables/plottable-financial.h', size 8622 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPFinancialData { public: QCPFinancialData(); QCPFinancialData(double key, double open, double high, double low, double close); inline double sortKey() const { return key; } inline static QCPFinancialData fromSortKey(double sortKey) { return QCPFinancialData(sortKey, 0, 0, 0, 0); } inline static bool sortKeyIsMainKey() { return true; } inline double mainKey() const { return key; } inline double mainValue() const { return open; } inline QCPRange valueRange() const { return QCPRange(low, high); // open and close must lie between low and high, so we don't need to check them } double key, open, high, low, close; }; Q_DECLARE_TYPEINFO(QCPFinancialData, Q_PRIMITIVE_TYPE); /*! \typedef QCPFinancialDataContainer Container for storing \ref QCPFinancialData points. The data is stored sorted by \a key. This template instantiation is the container in which QCPFinancial holds its data. For details about the generic container, see the documentation of the class template \ref QCPDataContainer. \see QCPFinancialData, QCPFinancial::setData */ typedef QCPDataContainer QCPFinancialDataContainer; class QCP_LIB_DECL QCPFinancial : public QCPAbstractPlottable1D { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(ChartStyle chartStyle READ chartStyle WRITE setChartStyle) Q_PROPERTY(double width READ width WRITE setWidth) Q_PROPERTY(WidthType widthType READ widthType WRITE setWidthType) Q_PROPERTY(bool twoColored READ twoColored WRITE setTwoColored) Q_PROPERTY(QBrush brushPositive READ brushPositive WRITE setBrushPositive) Q_PROPERTY(QBrush brushNegative READ brushNegative WRITE setBrushNegative) Q_PROPERTY(QPen penPositive READ penPositive WRITE setPenPositive) Q_PROPERTY(QPen penNegative READ penNegative WRITE setPenNegative) /// \endcond public: /*! Defines the ways the width of the financial bar can be specified. Thus it defines what the number passed to \ref setWidth actually means. \see setWidthType, setWidth */ enum WidthType { wtAbsolute ///< width is in absolute pixels , wtAxisRectRatio ///< width is given by a fraction of the axis rect size , wtPlotCoords ///< width is in key coordinates and thus scales with the key axis range }; Q_ENUM(WidthType) /*! Defines the possible representations of OHLC data in the plot. \see setChartStyle */ enum ChartStyle { csOhlc ///< Open-High-Low-Close bar representation , csCandlestick ///< Candlestick representation }; Q_ENUM(ChartStyle) explicit QCPFinancial(QCPAxis *keyAxis, QCPAxis *valueAxis); ~QCPFinancial() override; // getters: QSharedPointer data() const { return mDataContainer; } ChartStyle chartStyle() const { return mChartStyle; } double width() const { return mWidth; } WidthType widthType() const { return mWidthType; } bool twoColored() const { return mTwoColored; } QBrush brushPositive() const { return mBrushPositive; } QBrush brushNegative() const { return mBrushNegative; } QPen penPositive() const { return mPenPositive; } QPen penNegative() const { return mPenNegative; } // setters: void setData(QSharedPointer data); void setData(const QVector &keys, const QVector &open, const QVector &high, const QVector &low, const QVector &close, bool alreadySorted = false); void setChartStyle(ChartStyle style); void setWidth(double width); void setWidthType(WidthType widthType); void setTwoColored(bool twoColored); void setBrushPositive(const QBrush &brush); void setBrushNegative(const QBrush &brush); void setPenPositive(const QPen &pen); void setPenNegative(const QPen &pen); // non-property methods: void addData(const QVector &keys, const QVector &open, const QVector &high, const QVector &low, const QVector &close, bool alreadySorted = false); void addData(double key, double open, double high, double low, double close); // reimplemented virtual methods: QCPDataSelection selectTestRect(const QRectF &rect, bool onlySelectable) const override; - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain = QCP::sdBoth) const override; QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain = QCP::sdBoth, const QCPRange &inKeyRange = QCPRange()) const override; // static methods: static QCPFinancialDataContainer timeSeriesToOhlc(const QVector &time, const QVector &value, double timeBinSize, double timeBinOffset = 0); protected: // property members: ChartStyle mChartStyle; double mWidth; WidthType mWidthType; bool mTwoColored; QBrush mBrushPositive, mBrushNegative; QPen mPenPositive, mPenNegative; // reimplemented virtual methods: void draw(QCPPainter *painter) override; void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const override; // non-virtual methods: void drawOhlcPlot(QCPPainter *painter, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, bool isSelected); void drawCandlestickPlot(QCPPainter *painter, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, bool isSelected); double getPixelWidth(double key, double keyPixel) const; double ohlcSelectTest(const QPointF &pos, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, QCPFinancialDataContainer::const_iterator &closestDataPoint) const; double candlestickSelectTest(const QPointF &pos, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, QCPFinancialDataContainer::const_iterator &closestDataPoint) const; void getVisibleDataBounds(QCPFinancialDataContainer::const_iterator &begin, QCPFinancialDataContainer::const_iterator &end) const; QRectF selectionHitBox(QCPFinancialDataContainer::const_iterator it) const; friend class QCustomPlot; friend class QCPLegend; }; Q_DECLARE_METATYPE(QCPFinancial::ChartStyle) /* end of 'src/plottables/plottable-financial.h' */ /* including file 'src/plottables/plottable-errorbar.h', size 7567 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPErrorBarsData { public: QCPErrorBarsData(); explicit QCPErrorBarsData(double error); QCPErrorBarsData(double errorMinus, double errorPlus); double errorMinus, errorPlus; }; Q_DECLARE_TYPEINFO(QCPErrorBarsData, Q_PRIMITIVE_TYPE); /*! \typedef QCPErrorBarsDataContainer Container for storing \ref QCPErrorBarsData points. It is a typedef for QVector<\ref QCPErrorBarsData>. This is the container in which \ref QCPErrorBars holds its data. Unlike most other data containers for plottables, it is not based on \ref QCPDataContainer. This is because the error bars plottable is special in that it doesn't store its own key and value coordinate per error bar. It adopts the key and value from the plottable to which the error bars shall be applied (\ref QCPErrorBars::setDataPlottable). So the stored \ref QCPErrorBarsData doesn't need a sortable key, but merely an index (as \c QVector provides), which maps one-to-one to the indices of the other plottable's data. \see QCPErrorBarsData, QCPErrorBars::setData */ typedef QVector QCPErrorBarsDataContainer; class QCP_LIB_DECL QCPErrorBars : public QCPAbstractPlottable, public QCPPlottableInterface1D { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QSharedPointer data READ data WRITE setData) Q_PROPERTY(QCPAbstractPlottable *dataPlottable READ dataPlottable WRITE setDataPlottable) Q_PROPERTY(ErrorType errorType READ errorType WRITE setErrorType) Q_PROPERTY(double whiskerWidth READ whiskerWidth WRITE setWhiskerWidth) Q_PROPERTY(double symbolGap READ symbolGap WRITE setSymbolGap) /// \endcond public: /*! Defines in which orientation the error bars shall appear. If your data needs both error dimensions, create two \ref QCPErrorBars with different \ref ErrorType. \see setErrorType */ enum ErrorType { etKeyError ///< The errors are for the key dimension (bars appear parallel to the key axis) , etValueError ///< The errors are for the value dimension (bars appear parallel to the value axis) }; Q_ENUM(ErrorType) explicit QCPErrorBars(QCPAxis *keyAxis, QCPAxis *valueAxis); ~QCPErrorBars() override; // getters: QSharedPointer data() const { return mDataContainer; } QCPAbstractPlottable *dataPlottable() const { return mDataPlottable.data(); } ErrorType errorType() const { return mErrorType; } double whiskerWidth() const { return mWhiskerWidth; } double symbolGap() const { return mSymbolGap; } // setters: void setData(QSharedPointer data); void setData(const QVector &error); void setData(const QVector &errorMinus, const QVector &errorPlus); void setDataPlottable(QCPAbstractPlottable *plottable); void setErrorType(ErrorType type); void setWhiskerWidth(double pixels); void setSymbolGap(double pixels); // non-property methods: void addData(const QVector &error); void addData(const QVector &errorMinus, const QVector &errorPlus); void addData(double error); void addData(double errorMinus, double errorPlus); // virtual methods of 1d plottable interface: int dataCount() const override; double dataMainKey(int index) const override; double dataSortKey(int index) const override; double dataMainValue(int index) const override; QCPRange dataValueRange(int index) const override; QPointF dataPixelPosition(int index) const override; bool sortKeyIsMainKey() const override; QCPDataSelection selectTestRect(const QRectF &rect, bool onlySelectable) const override; int findBegin(double sortKey, bool expandedRange = true) const override; int findEnd(double sortKey, bool expandedRange = true) const override; // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; QCPPlottableInterface1D *interface1D() override { return this; } protected: // property members: QSharedPointer mDataContainer; QPointer mDataPlottable; ErrorType mErrorType; double mWhiskerWidth; double mSymbolGap; // reimplemented virtual methods: void draw(QCPPainter *painter) override; void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const override; QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain = QCP::sdBoth) const override; QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain = QCP::sdBoth, const QCPRange &inKeyRange = QCPRange()) const override; // non-virtual methods: void getErrorBarLines(QCPErrorBarsDataContainer::const_iterator it, QVector &backbones, QVector &whiskers) const; void getVisibleDataBounds(QCPErrorBarsDataContainer::const_iterator &begin, QCPErrorBarsDataContainer::const_iterator &end, const QCPDataRange &rangeRestriction) const; double pointDistance(const QPointF &pixelPoint, QCPErrorBarsDataContainer::const_iterator &closestData) const; // helpers: void getDataSegments(QList &selectedSegments, QList &unselectedSegments) const; bool errorBarVisible(int index) const; bool rectIntersectsLine(const QRectF &pixelRect, const QLineF &line) const; friend class QCustomPlot; friend class QCPLegend; }; /* end of 'src/plottables/plottable-errorbar.h' */ /* including file 'src/items/item-straightline.h', size 3117 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPItemStraightLine : public QCPAbstractItem { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QPen pen READ pen WRITE setPen) Q_PROPERTY(QPen selectedPen READ selectedPen WRITE setSelectedPen) /// \endcond public: explicit QCPItemStraightLine(QCustomPlot *parentPlot); ~QCPItemStraightLine() override; // getters: QPen pen() const { return mPen; } QPen selectedPen() const { return mSelectedPen; } // setters; void setPen(const QPen &pen); void setSelectedPen(const QPen &pen); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; QCPItemPosition *const point1; QCPItemPosition *const point2; protected: // property members: QPen mPen, mSelectedPen; // reimplemented virtual methods: void draw(QCPPainter *painter) override; // non-virtual methods: QLineF getRectClippedStraightLine(const QCPVector2D &point1, const QCPVector2D &vec, const QRect &rect) const; QPen mainPen() const; }; /* end of 'src/items/item-straightline.h' */ /* including file 'src/items/item-line.h', size 3407 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPItemLine : public QCPAbstractItem { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QPen pen READ pen WRITE setPen) Q_PROPERTY(QPen selectedPen READ selectedPen WRITE setSelectedPen) Q_PROPERTY(QCPLineEnding head READ head WRITE setHead) Q_PROPERTY(QCPLineEnding tail READ tail WRITE setTail) /// \endcond public: explicit QCPItemLine(QCustomPlot *parentPlot); ~QCPItemLine() override; // getters: QPen pen() const { return mPen; } QPen selectedPen() const { return mSelectedPen; } QCPLineEnding head() const { return mHead; } QCPLineEnding tail() const { return mTail; } // setters; void setPen(const QPen &pen); void setSelectedPen(const QPen &pen); void setHead(const QCPLineEnding &head); void setTail(const QCPLineEnding &tail); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; QCPItemPosition *const start; QCPItemPosition *const end; protected: // property members: QPen mPen, mSelectedPen; QCPLineEnding mHead, mTail; // reimplemented virtual methods: void draw(QCPPainter *painter) override; // non-virtual methods: QLineF getRectClippedLine(const QCPVector2D &start, const QCPVector2D &end, const QRect &rect) const; QPen mainPen() const; }; /* end of 'src/items/item-line.h' */ /* including file 'src/items/item-curve.h', size 3379 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPItemCurve : public QCPAbstractItem { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QPen pen READ pen WRITE setPen) Q_PROPERTY(QPen selectedPen READ selectedPen WRITE setSelectedPen) Q_PROPERTY(QCPLineEnding head READ head WRITE setHead) Q_PROPERTY(QCPLineEnding tail READ tail WRITE setTail) /// \endcond public: explicit QCPItemCurve(QCustomPlot *parentPlot); ~QCPItemCurve() override; // getters: QPen pen() const { return mPen; } QPen selectedPen() const { return mSelectedPen; } QCPLineEnding head() const { return mHead; } QCPLineEnding tail() const { return mTail; } // setters; void setPen(const QPen &pen); void setSelectedPen(const QPen &pen); void setHead(const QCPLineEnding &head); void setTail(const QCPLineEnding &tail); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; QCPItemPosition *const start; QCPItemPosition *const startDir; QCPItemPosition *const endDir; QCPItemPosition *const end; protected: // property members: QPen mPen, mSelectedPen; QCPLineEnding mHead, mTail; // reimplemented virtual methods: void draw(QCPPainter *painter) override; // non-virtual methods: QPen mainPen() const; }; /* end of 'src/items/item-curve.h' */ /* including file 'src/items/item-rect.h', size 3688 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPItemRect : public QCPAbstractItem { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QPen pen READ pen WRITE setPen) Q_PROPERTY(QPen selectedPen READ selectedPen WRITE setSelectedPen) Q_PROPERTY(QBrush brush READ brush WRITE setBrush) Q_PROPERTY(QBrush selectedBrush READ selectedBrush WRITE setSelectedBrush) /// \endcond public: explicit QCPItemRect(QCustomPlot *parentPlot); ~QCPItemRect() override; // getters: QPen pen() const { return mPen; } QPen selectedPen() const { return mSelectedPen; } QBrush brush() const { return mBrush; } QBrush selectedBrush() const { return mSelectedBrush; } // setters; void setPen(const QPen &pen); void setSelectedPen(const QPen &pen); void setBrush(const QBrush &brush); void setSelectedBrush(const QBrush &brush); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; QCPItemPosition *const topLeft; QCPItemPosition *const bottomRight; QCPItemAnchor *const top; QCPItemAnchor *const topRight; QCPItemAnchor *const right; QCPItemAnchor *const bottom; QCPItemAnchor *const bottomLeft; QCPItemAnchor *const left; protected: enum AnchorIndex { aiTop, aiTopRight, aiRight, aiBottom, aiBottomLeft, aiLeft }; // property members: QPen mPen, mSelectedPen; QBrush mBrush, mSelectedBrush; // reimplemented virtual methods: void draw(QCPPainter *painter) override; QPointF anchorPixelPosition(int anchorId) const override; // non-virtual methods: QPen mainPen() const; QBrush mainBrush() const; }; /* end of 'src/items/item-rect.h' */ /* including file 'src/items/item-text.h', size 5554 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPItemText : public QCPAbstractItem { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QColor color READ color WRITE setColor) Q_PROPERTY(QColor selectedColor READ selectedColor WRITE setSelectedColor) Q_PROPERTY(QPen pen READ pen WRITE setPen) Q_PROPERTY(QPen selectedPen READ selectedPen WRITE setSelectedPen) Q_PROPERTY(QBrush brush READ brush WRITE setBrush) Q_PROPERTY(QBrush selectedBrush READ selectedBrush WRITE setSelectedBrush) Q_PROPERTY(QFont font READ font WRITE setFont) Q_PROPERTY(QFont selectedFont READ selectedFont WRITE setSelectedFont) Q_PROPERTY(QString text READ text WRITE setText) Q_PROPERTY(Qt::Alignment positionAlignment READ positionAlignment WRITE setPositionAlignment) Q_PROPERTY(Qt::Alignment textAlignment READ textAlignment WRITE setTextAlignment) Q_PROPERTY(double rotation READ rotation WRITE setRotation) Q_PROPERTY(QMargins padding READ padding WRITE setPadding) /// \endcond public: explicit QCPItemText(QCustomPlot *parentPlot); ~QCPItemText() override; // getters: QColor color() const { return mColor; } QColor selectedColor() const { return mSelectedColor; } QPen pen() const { return mPen; } QPen selectedPen() const { return mSelectedPen; } QBrush brush() const { return mBrush; } QBrush selectedBrush() const { return mSelectedBrush; } QFont font() const { return mFont; } QFont selectedFont() const { return mSelectedFont; } QString text() const { return mText; } Qt::Alignment positionAlignment() const { return mPositionAlignment; } Qt::Alignment textAlignment() const { return mTextAlignment; } double rotation() const { return mRotation; } QMargins padding() const { return mPadding; } // setters; void setColor(const QColor &color); void setSelectedColor(const QColor &color); void setPen(const QPen &pen); void setSelectedPen(const QPen &pen); void setBrush(const QBrush &brush); void setSelectedBrush(const QBrush &brush); void setFont(const QFont &font); void setSelectedFont(const QFont &font); void setText(const QString &text); void setPositionAlignment(Qt::Alignment alignment); void setTextAlignment(Qt::Alignment alignment); void setRotation(double degrees); void setPadding(const QMargins &padding); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; QCPItemPosition *const position; QCPItemAnchor *const topLeft; QCPItemAnchor *const top; QCPItemAnchor *const topRight; QCPItemAnchor *const right; QCPItemAnchor *const bottomRight; QCPItemAnchor *const bottom; QCPItemAnchor *const bottomLeft; QCPItemAnchor *const left; protected: enum AnchorIndex { aiTopLeft, aiTop, aiTopRight, aiRight, aiBottomRight, aiBottom, aiBottomLeft, aiLeft }; // property members: QColor mColor, mSelectedColor; QPen mPen, mSelectedPen; QBrush mBrush, mSelectedBrush; QFont mFont, mSelectedFont; QString mText; Qt::Alignment mPositionAlignment; Qt::Alignment mTextAlignment; double mRotation; QMargins mPadding; // reimplemented virtual methods: void draw(QCPPainter *painter) override; QPointF anchorPixelPosition(int anchorId) const override; // non-virtual methods: QPointF getTextDrawPoint(const QPointF &pos, const QRectF &rect, Qt::Alignment positionAlignment) const; QFont mainFont() const; QColor mainColor() const; QPen mainPen() const; QBrush mainBrush() const; }; /* end of 'src/items/item-text.h' */ /* including file 'src/items/item-ellipse.h', size 3868 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPItemEllipse : public QCPAbstractItem { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QPen pen READ pen WRITE setPen) Q_PROPERTY(QPen selectedPen READ selectedPen WRITE setSelectedPen) Q_PROPERTY(QBrush brush READ brush WRITE setBrush) Q_PROPERTY(QBrush selectedBrush READ selectedBrush WRITE setSelectedBrush) /// \endcond public: explicit QCPItemEllipse(QCustomPlot *parentPlot); ~QCPItemEllipse() override; // getters: QPen pen() const { return mPen; } QPen selectedPen() const { return mSelectedPen; } QBrush brush() const { return mBrush; } QBrush selectedBrush() const { return mSelectedBrush; } // setters; void setPen(const QPen &pen); void setSelectedPen(const QPen &pen); void setBrush(const QBrush &brush); void setSelectedBrush(const QBrush &brush); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; QCPItemPosition *const topLeft; QCPItemPosition *const bottomRight; QCPItemAnchor *const topLeftRim; QCPItemAnchor *const top; QCPItemAnchor *const topRightRim; QCPItemAnchor *const right; QCPItemAnchor *const bottomRightRim; QCPItemAnchor *const bottom; QCPItemAnchor *const bottomLeftRim; QCPItemAnchor *const left; QCPItemAnchor *const center; protected: enum AnchorIndex { aiTopLeftRim, aiTop, aiTopRightRim, aiRight, aiBottomRightRim, aiBottom, aiBottomLeftRim, aiLeft, aiCenter }; // property members: QPen mPen, mSelectedPen; QBrush mBrush, mSelectedBrush; // reimplemented virtual methods: void draw(QCPPainter *painter) override; QPointF anchorPixelPosition(int anchorId) const override; // non-virtual methods: QPen mainPen() const; QBrush mainBrush() const; }; /* end of 'src/items/item-ellipse.h' */ /* including file 'src/items/item-pixmap.h', size 4373 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPItemPixmap : public QCPAbstractItem { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QPixmap pixmap READ pixmap WRITE setPixmap) Q_PROPERTY(bool scaled READ scaled WRITE setScaled) Q_PROPERTY(Qt::AspectRatioMode aspectRatioMode READ aspectRatioMode) Q_PROPERTY(Qt::TransformationMode transformationMode READ transformationMode) Q_PROPERTY(QPen pen READ pen WRITE setPen) Q_PROPERTY(QPen selectedPen READ selectedPen WRITE setSelectedPen) /// \endcond public: explicit QCPItemPixmap(QCustomPlot *parentPlot); ~QCPItemPixmap() override; // getters: QPixmap pixmap() const { return mPixmap; } bool scaled() const { return mScaled; } Qt::AspectRatioMode aspectRatioMode() const { return mAspectRatioMode; } Qt::TransformationMode transformationMode() const { return mTransformationMode; } QPen pen() const { return mPen; } QPen selectedPen() const { return mSelectedPen; } // setters; void setPixmap(const QPixmap &pixmap); void setScaled(bool scaled, Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio, Qt::TransformationMode transformationMode = Qt::SmoothTransformation); void setPen(const QPen &pen); void setSelectedPen(const QPen &pen); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; QCPItemPosition *const topLeft; QCPItemPosition *const bottomRight; QCPItemAnchor *const top; QCPItemAnchor *const topRight; QCPItemAnchor *const right; QCPItemAnchor *const bottom; QCPItemAnchor *const bottomLeft; QCPItemAnchor *const left; protected: enum AnchorIndex { aiTop, aiTopRight, aiRight, aiBottom, aiBottomLeft, aiLeft }; // property members: QPixmap mPixmap; QPixmap mScaledPixmap; bool mScaled; bool mScaledPixmapInvalidated; Qt::AspectRatioMode mAspectRatioMode; Qt::TransformationMode mTransformationMode; QPen mPen, mSelectedPen; // reimplemented virtual methods: void draw(QCPPainter *painter) override; QPointF anchorPixelPosition(int anchorId) const override; // non-virtual methods: void updateScaledPixmap(QRect finalRect = QRect(), bool flipHorz = false, bool flipVert = false); - QRect getFinalRect(bool *flippedHorz = 0, bool *flippedVert = 0) const; + QRect getFinalRect(bool *flippedHorz = nullptr, bool *flippedVert = nullptr) const; QPen mainPen() const; }; /* end of 'src/items/item-pixmap.h' */ /* including file 'src/items/item-tracer.h', size 4762 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPItemTracer : public QCPAbstractItem { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QPen pen READ pen WRITE setPen) Q_PROPERTY(QPen selectedPen READ selectedPen WRITE setSelectedPen) Q_PROPERTY(QBrush brush READ brush WRITE setBrush) Q_PROPERTY(QBrush selectedBrush READ selectedBrush WRITE setSelectedBrush) Q_PROPERTY(double size READ size WRITE setSize) Q_PROPERTY(TracerStyle style READ style WRITE setStyle) Q_PROPERTY(QCPGraph *graph READ graph WRITE setGraph) Q_PROPERTY(double graphKey READ graphKey WRITE setGraphKey) Q_PROPERTY(bool interpolating READ interpolating WRITE setInterpolating) /// \endcond public: /*! The different visual appearances a tracer item can have. Some styles size may be controlled with \ref setSize. \see setStyle */ enum TracerStyle { tsNone ///< The tracer is not visible , tsPlus ///< A plus shaped crosshair with limited size , tsCrosshair ///< A plus shaped crosshair which spans the complete axis rect , tsCircle ///< A circle , tsSquare ///< A square }; Q_ENUM(TracerStyle) explicit QCPItemTracer(QCustomPlot *parentPlot); ~QCPItemTracer() override; // getters: QPen pen() const { return mPen; } QPen selectedPen() const { return mSelectedPen; } QBrush brush() const { return mBrush; } QBrush selectedBrush() const { return mSelectedBrush; } double size() const { return mSize; } TracerStyle style() const { return mStyle; } QCPGraph *graph() const { return mGraph; } double graphKey() const { return mGraphKey; } bool interpolating() const { return mInterpolating; } // setters; void setPen(const QPen &pen); void setSelectedPen(const QPen &pen); void setBrush(const QBrush &brush); void setSelectedBrush(const QBrush &brush); void setSize(double size); void setStyle(TracerStyle style); void setGraph(QCPGraph *graph); void setGraphKey(double key); void setInterpolating(bool enabled); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; // non-virtual methods: void updatePosition(); QCPItemPosition *const position; protected: // property members: QPen mPen, mSelectedPen; QBrush mBrush, mSelectedBrush; double mSize; TracerStyle mStyle; QCPGraph *mGraph; double mGraphKey; bool mInterpolating; // reimplemented virtual methods: void draw(QCPPainter *painter) override; // non-virtual methods: QPen mainPen() const; QBrush mainBrush() const; }; Q_DECLARE_METATYPE(QCPItemTracer::TracerStyle) /* end of 'src/items/item-tracer.h' */ /* including file 'src/items/item-bracket.h', size 3969 */ /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */ class QCP_LIB_DECL QCPItemBracket : public QCPAbstractItem { Q_OBJECT /// \cond INCLUDE_QPROPERTIES Q_PROPERTY(QPen pen READ pen WRITE setPen) Q_PROPERTY(QPen selectedPen READ selectedPen WRITE setSelectedPen) Q_PROPERTY(double length READ length WRITE setLength) Q_PROPERTY(BracketStyle style READ style WRITE setStyle) /// \endcond public: /*! Defines the various visual shapes of the bracket item. The appearance can be further modified by \ref setLength and \ref setPen. \see setStyle */ enum BracketStyle { bsSquare ///< A brace with angled edges , bsRound ///< A brace with round edges , bsCurly ///< A curly brace , bsCalligraphic ///< A curly brace with varying stroke width giving a calligraphic impression }; Q_ENUM(BracketStyle) explicit QCPItemBracket(QCustomPlot *parentPlot); ~QCPItemBracket() override; // getters: QPen pen() const { return mPen; } QPen selectedPen() const { return mSelectedPen; } double length() const { return mLength; } BracketStyle style() const { return mStyle; } // setters; void setPen(const QPen &pen); void setSelectedPen(const QPen &pen); void setLength(double length); void setStyle(BracketStyle style); // reimplemented virtual methods: - double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = 0) const override; + double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details = nullptr) const override; QCPItemPosition *const left; QCPItemPosition *const right; QCPItemAnchor *const center; protected: // property members: enum AnchorIndex { aiCenter }; QPen mPen, mSelectedPen; double mLength; BracketStyle mStyle; // reimplemented virtual methods: void draw(QCPPainter *painter) override; QPointF anchorPixelPosition(int anchorId) const override; // non-virtual methods: QPen mainPen() const; }; Q_DECLARE_METATYPE(QCPItemBracket::BracketStyle) /* end of 'src/items/item-bracket.h' */ #endif // QCUSTOMPLOT_H diff --git a/kstars/auxiliary/skyobjectlistmodel.h b/kstars/auxiliary/skyobjectlistmodel.h index daa4cc626..2fe49b4c3 100644 --- a/kstars/auxiliary/skyobjectlistmodel.h +++ b/kstars/auxiliary/skyobjectlistmodel.h @@ -1,68 +1,68 @@ /*************************************************************************** skyobjectlistmodel.h - K Desktop Planetarium ------------------- begin : Wed Jul 29 2016 copyright : (C) 2016 by Artem Fedoskin email : afedoskin3@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. * * * ***************************************************************************/ #ifndef SKYOBJECTLISTMODEL_H_ #define SKYOBJECTLISTMODEL_H_ #include #include class SkyObject; /** @class SkyObjectListModel * A model used in Find Object Dialog in QML. Each entry is a QString (name of object) and pointer to * SkyObject itself * * @short Model that is used in Find Object Dialog * @author Artem Fedoskin, Jason Harris * @version 1.0 */ class SkyObjectListModel : public QAbstractListModel { Q_OBJECT public: enum DemoRoles { SkyObjectRole = Qt::UserRole + 1, }; - explicit SkyObjectListModel(QObject *parent = 0); + explicit SkyObjectListModel(QObject *parent = nullptr); int rowCount(const QModelIndex &) const override { return skyObjects.size(); } QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; /** * @return index of object from skyObjects with name objectName. -1 if object with such * name was not found */ int indexOf(QString objectName) const; /** * @short filter * @param regEx * @return */ QStringList filter(QRegExp regEx); void setSkyObjectsList(QVector> sObjects); private: QVector> skyObjects; }; #endif diff --git a/kstars/auxiliary/thumbnailpicker.cpp b/kstars/auxiliary/thumbnailpicker.cpp index 0c3b6146d..9b189550a 100644 --- a/kstars/auxiliary/thumbnailpicker.cpp +++ b/kstars/auxiliary/thumbnailpicker.cpp @@ -1,384 +1,384 @@ /*************************************************************************** thumbnailpicker.cpp - description ------------------- begin : Thu Mar 2 2005 copyright : (C) 2005 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 "thumbnailpicker.h" #include "ksutils.h" #include "thumbnaileditor.h" #include "dialogs/detaildialog.h" #include "skyobjects/skyobject.h" #include #include #include #include #include #include #include #include #include ThumbnailPickerUI::ThumbnailPickerUI(QWidget *parent) : QFrame(parent) { setupUi(this); } ThumbnailPicker::ThumbnailPicker(SkyObject *o, const QPixmap ¤t, QWidget *parent, double _w, double _h, QString cap) : QDialog(parent), SelectedImageIndex(-1), Object(o), bImageFound(false) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif thumbWidth = _w; thumbHeight = _h; Image = new QPixmap(current.scaled(_w, _h, Qt::KeepAspectRatio, Qt::FastTransformation)); ImageRect = new QRect(0, 0, 200, 200); ui = new ThumbnailPickerUI(this); setWindowTitle(cap); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(ui); setLayout(mainLayout); 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())); ui->CurrentImage->setPixmap(*Image); connect(ui->EditButton, SIGNAL(clicked()), this, SLOT(slotEditImage())); connect(ui->UnsetButton, SIGNAL(clicked()), this, SLOT(slotUnsetImage())); connect(ui->ImageList, SIGNAL(currentRowChanged(int)), this, SLOT(slotSetFromList(int))); connect(ui->ImageURLBox, SIGNAL(urlSelected(QUrl)), this, SLOT(slotSetFromURL())); connect(ui->ImageURLBox, SIGNAL(returnPressed()), this, SLOT(slotSetFromURL())); //ui->ImageURLBox->lineEdit()->setTrapReturnKey( true ); ui->EditButton->setEnabled(false); slotFillList(); } ThumbnailPicker::~ThumbnailPicker() { while (!PixList.isEmpty()) delete PixList.takeFirst(); } //Query online sources for images of the object void ThumbnailPicker::slotFillList() { //Query Google Image Search: //Search for the primary name, or longname and primary name QString sName = QString("%1 ").arg(Object->name()); if (Object->longname() != Object->name()) { sName = QString("%1 ").arg(Object->longname()) + sName; } QString query = QString("http://www.google.com/search?q=%1&tbs=itp:photo,isz:ex,iszw:200,iszh:200&tbm=isch&source=lnt") .arg(sName); QUrlQuery gURL(query); //gURL.addQueryItem( "q", sName ); //add the Google-image query string parseGooglePage(gURL.query()); } void ThumbnailPicker::slotProcessGoogleResult(KJob *result) { //Preload ImageList with the URLs in the object's ImageList: QStringList ImageList(Object->ImageList()); if (result->error()) { result->uiDelegate()->showErrorMessage(); result->kill(); return; } QString PageHTML(static_cast(result)->data()); int index = PageHTML.indexOf("src=\"http:", 0); while (index >= 0) { index += 5; //move to end of "src=\"http:" marker //Image URL is everything from index to next occurrence of "\"" ImageList.append(PageHTML.mid(index, PageHTML.indexOf("\"", index) - index)); index = PageHTML.indexOf("src=\"http:", index); } //Total Number of images to be loaded: int nImages = ImageList.count(); if (nImages) { ui->SearchProgress->setMinimum(0); ui->SearchProgress->setMaximum(nImages - 1); ui->SearchLabel->setText(i18n("Loading images...")); } else { close(); return; } //Add images from the ImageList for (int i = 0; i < ImageList.size(); ++i) { QUrl u(ImageList[i]); if (u.isValid()) { KIO::StoredTransferJob *j = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); - j->setUiDelegate(0); + j->setUiDelegate(nullptr); connect(j, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*))); } } } void ThumbnailPicker::slotJobResult(KJob *job) { KIO::StoredTransferJob *stjob = (KIO::StoredTransferJob *)job; //Update Progressbar if (!ui->SearchProgress->isHidden()) { ui->SearchProgress->setValue(ui->SearchProgress->value() + 1); if (ui->SearchProgress->value() == ui->SearchProgress->maximum()) { ui->SearchProgress->hide(); ui->SearchLabel->setText(i18n("Search results:")); } } //If there was a problem, just return silently without adding image to list. if (job->error()) { qDebug() << " error=" << job->error(); job->kill(); return; } QPixmap *pm = new QPixmap(); pm->loadFromData(stjob->data()); uint w = pm->width(); uint h = pm->height(); uint pad = 0; /*FIXME LATER 4* QDialogBase::marginHint() + 2*ui->SearchLabel->height() + QDialogBase::actionButton( QDialogBase::Ok )->height() + 25;*/ uint hDesk = QApplication::desktop()->availableGeometry().height() - pad; if (h > hDesk) *pm = pm->scaled(w * hDesk / h, hDesk, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); PixList.append(pm); //Add 50x50 image and URL to listbox //ui->ImageList->insertItem( shrinkImage( PixList.last(), 50 ), // cjob->srcURLs().first().prettyUrl() ); ui->ImageList->addItem(new QListWidgetItem(QIcon(shrinkImage(PixList.last(), 200)), stjob->url().url())); } //void ThumbnailPicker::parseGooglePage( QStringList &ImList, const QString &URL ) void ThumbnailPicker::parseGooglePage(const QString &URL) { QUrl googleURL(URL); KIO::StoredTransferJob *job = KIO::storedGet(googleURL); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotProcessGoogleResult(KJob*))); job->start(); } QPixmap ThumbnailPicker::shrinkImage(QPixmap *pm, int size, bool setImage) { int w(pm->width()), h(pm->height()); int bigSize(w); int rx(0), ry(0), sx(0), sy(0), bx(0), by(0); if (size == 0) return QPixmap(); //Prepare variables for rescaling image (if it is larger than 'size') if (w > size && w >= h) { h = size; w = size * pm->width() / pm->height(); } else if (h > size && h > w) { w = size; h = size * pm->height() / pm->width(); } sx = (w - size) / 2; sy = (h - size) / 2; if (sx < 0) { rx = -sx; sx = 0; } if (sy < 0) { ry = -sy; sy = 0; } if (setImage) bigSize = int(200. * float(pm->width()) / float(w)); QPixmap result(size, size); result.fill(Qt::black); //in case final image is smaller than 'size' if (pm->width() > size || pm->height() > size) //image larger than 'size'? { //convert to QImage so we can smoothscale it QImage im(pm->toImage()); im = im.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); //bitBlt sizexsize square section of image QPainter p; p.begin(&result); p.drawImage(rx, ry, im, sx, sy, size, size); p.end(); if (setImage) { bx = int(sx * float(pm->width()) / float(w)); by = int(sy * float(pm->width()) / float(w)); ImageRect->setRect(bx, by, bigSize, bigSize); } } else //image is smaller than size x size { QPainter p; p.begin(&result); p.drawImage(rx, ry, pm->toImage()); p.end(); if (setImage) { bx = int(rx * float(pm->width()) / float(w)); by = int(ry * float(pm->width()) / float(w)); ImageRect->setRect(bx, by, bigSize, bigSize); } } return result; } void ThumbnailPicker::slotEditImage() { QPointer te = new ThumbnailEditor(this, thumbWidth, thumbHeight); if (te->exec() == QDialog::Accepted) { QPixmap pm = te->thumbnail(); *Image = pm; ui->CurrentImage->setPixmap(pm); ui->CurrentImage->update(); } delete te; } void ThumbnailPicker::slotUnsetImage() { // QFile file; //if ( KSUtils::openDataFile( file, "noimage.png" ) ) { // file.close(); // Image->load( file.fileName(), "PNG" ); // } else { // *Image = Image->scaled( dd->thumbnail()->width(), dd->thumbnail()->height() ); // Image->fill( dd->palette().color( QPalette::Window ) ); // } QPixmap noImage; noImage.load(":/images/noimage.png"); Image = new QPixmap(noImage.scaled(thumbWidth, thumbHeight, Qt::KeepAspectRatio, Qt::FastTransformation)); ui->EditButton->setEnabled(false); ui->CurrentImage->setPixmap(*Image); ui->CurrentImage->update(); bImageFound = false; } void ThumbnailPicker::slotSetFromList(int i) { //Display image in preview pane QPixmap pm; pm = shrinkImage(PixList[i], 200, true); //scale image SelectedImageIndex = i; ui->CurrentImage->setPixmap(pm); ui->CurrentImage->update(); ui->EditButton->setEnabled(true); *Image = PixList[i]->scaled(thumbWidth, thumbHeight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); bImageFound = true; } void ThumbnailPicker::slotSetFromURL() { //Attempt to load the specified URL QUrl u = ui->ImageURLBox->url(); if (u.isValid()) { if (u.isLocalFile()) { QFile localFile(u.toLocalFile()); //Add image to list //If image is taller than desktop, rescale it. QImage im(localFile.fileName()); if (im.isNull()) { - KMessageBox::sorry(0, i18n("Failed to load image at %1", localFile.fileName()), + KMessageBox::sorry(nullptr, i18n("Failed to load image at %1", localFile.fileName()), i18n("Failed to load image")); return; } uint w = im.width(); uint h = im.height(); uint pad = 0; /* FIXME later 4*marginHint() + 2*ui->SearchLabel->height() + actionButton( Ok )->height() + 25; */ uint hDesk = QApplication::desktop()->availableGeometry().height() - pad; if (h > hDesk) im = im.scaled(w * hDesk / h, hDesk, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); //Add Image to top of list and 50x50 thumbnail image and URL to top of listbox PixList.insert(0, new QPixmap(QPixmap::fromImage(im))); ui->ImageList->insertItem(0, new QListWidgetItem(QIcon(shrinkImage(PixList.last(), 50)), u.url())); //Select the new image ui->ImageList->setCurrentRow(0); slotSetFromList(0); } else { KIO::StoredTransferJob *j = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); - j->setUiDelegate(0); + j->setUiDelegate(nullptr); connect(j, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*))); } } } diff --git a/kstars/auxiliary/thumbnailpicker.h b/kstars/auxiliary/thumbnailpicker.h index 5729f12af..2711df17d 100644 --- a/kstars/auxiliary/thumbnailpicker.h +++ b/kstars/auxiliary/thumbnailpicker.h @@ -1,81 +1,81 @@ /*************************************************************************** thumbnailpicker.h - description ------------------- begin : Thu Mar 2 2005 copyright : (C) 2005 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. * * * ***************************************************************************/ #pragma once #include "ui_thumbnailpicker.h" #include #include #include class QRect; class KJob; class DetailDialog; class SkyObject; class ThumbnailPickerUI : public QFrame, public Ui::ThumbnailPicker { Q_OBJECT public: explicit ThumbnailPickerUI(QWidget *p); }; /** * @short Dialog for modifying an object's thumbnail image * * @author Jason Harris */ class ThumbnailPicker : public QDialog { Q_OBJECT public: - ThumbnailPicker(SkyObject *o, const QPixmap ¤t, QWidget *parent = 0, double w = 200, double h = 200, + ThumbnailPicker(SkyObject *o, const QPixmap ¤t, QWidget *parent = nullptr, double w = 200, double h = 200, QString cap = i18n("Choose Thumbnail Image")); ~ThumbnailPicker() override; QPixmap *image() { return Image; } QPixmap *currentListImage() { return PixList.at(SelectedImageIndex); } bool imageFound() const { return bImageFound; } QRect *imageRect() const { return ImageRect; } private slots: void slotEditImage(); void slotUnsetImage(); void slotSetFromList(int i); void slotSetFromURL(); void slotFillList(); void slotProcessGoogleResult(KJob *); /** Make sure download has finished, then make sure file exists, then add image to list */ void slotJobResult(KJob *); private: QPixmap shrinkImage(QPixmap *original, int newSize, bool setImage = false); void parseGooglePage(const QString &URL); int SelectedImageIndex; double thumbWidth, thumbHeight; ThumbnailPickerUI *ui; QPixmap *Image; SkyObject *Object; QList JobList; QList PixList; bool bImageFound; QRect *ImageRect; }; diff --git a/kstars/dialogs/addcatdialog.cpp b/kstars/dialogs/addcatdialog.cpp index 6d6e9b75e..0335161a6 100644 --- a/kstars/dialogs/addcatdialog.cpp +++ b/kstars/dialogs/addcatdialog.cpp @@ -1,259 +1,259 @@ /*************************************************************************** addcatdialog.cpp - description ------------------- begin : Sun Mar 3 2002 copyright : (C) 2002 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 "addcatdialog.h" #include "kstarsdata.h" #include "skycomponents/skymapcomposite.h" #include #include #include #include AddCatDialogUI::AddCatDialogUI(QWidget *parent) : QFrame(parent) { setupUi(this); } AddCatDialog::AddCatDialog(QWidget *parent) : QDialog(parent) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif QDir::setCurrent(QDir::homePath()); acd = new AddCatDialogUI(this); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(acd); setLayout(mainLayout); setWindowTitle(i18n("Import Catalog")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel); mainLayout->addWidget(buttonBox); connect(acd->DataURL->lineEdit(), SIGNAL(lostFocus()), this, SLOT(slotShowDataFile())); connect(acd->DataURL, SIGNAL(urlSelected(QUrl)), this, SLOT(slotShowDataFile())); connect(acd->PreviewButton, SIGNAL(clicked()), this, SLOT(slotPreviewCatalog())); connect(buttonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(slotCreateCatalog())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(close())); connect(buttonBox, SIGNAL(helpRequested()), this, SLOT(slotHelp())); acd->FieldList->addItem(i18n("ID Number")); acd->FieldList->addItem(i18n("Right Ascension")); acd->FieldList->addItem(i18n("Declination")); acd->FieldList->addItem(i18n("Object Type")); acd->FieldPool->addItem(i18n("Common Name")); acd->FieldPool->addItem(i18n("Magnitude")); acd->FieldPool->addItem(i18n("Flux")); acd->FieldPool->addItem(i18n("Major Axis")); acd->FieldPool->addItem(i18n("Minor Axis")); acd->FieldPool->addItem(i18n("Position Angle")); acd->FieldPool->addItem(i18n("Ignore")); } void AddCatDialog::slotHelp() { QString message = i18n("A valid custom catalog file has one line per object, " "with the following fields in each line:") + "\n\t" + i18n("1. Type identifier. Must be one of: 0 (star), 3 (open cluster), 4 (globular cluster), " "5 (gaseous nebula), 6 (planetary nebula), 7 (supernova remnant), or 8 (galaxy)") + "\n\t" + i18n("2. Right Ascension (floating-point value)") + "\n\t" + i18n("3. Declination (floating-point value)") + "\n\t" + i18n("4. Magnitude (floating-point value)") + "\n\t" + i18n("5. Integrated Flux (floating-point value); frequency and units are set separately in the catalog file.") + "\n\t" + i18n("6. Spectral type (if type=0); otherwise object's catalog name") + "\n\t" + i18n("7. Star name (if type=0); otherwise object's common name. [field 7 is optional]") + "\n\n" + i18n("The fields should be separated by whitespace. In addition, the catalog " "may contain comment lines beginning with \'#\'."); - KMessageBox::information(0, message, i18n("Help on custom catalog file format")); + KMessageBox::information(nullptr, message, i18n("Help on custom catalog file format")); } /* Attempt to parse the catalog data file specified in the DataURL box. * We assume the data file has space-separated fields, and that each line has * the data fields listed in the Catalog fields list, in the same order. * Each data field is parsed as follows: * * ID number: integer value * Right Ascension: colon-delimited hh:mm:ss.s or floating-point value * Declination: colon-delimited dd:mm:ss.s or floating-point value * Object type: integer value, one of [ 0,1,2,3,4,5,6,7,8 ] * Common name: string value (if it contains a space, it *must* be enclosed in quotes!) * Magnitude: floating-point value * Major axis: floating-point value (length of major axis in arcmin) * Minor axis: floating-point value (length of minor axis in arcmin) * Position angle: floating-point value (position angle, in degrees) */ bool AddCatDialog::validateDataFile() { //Create the catalog file contents: first the header CatalogContents = writeCatalogHeader(); // Check if the URL is empty if (acd->DataURL->url().isEmpty()) return false; //Next, the data lines (fill from user-specified file) QFile dataFile(acd->DataURL->url().toLocalFile()); if (dataFile.open(QIODevice::ReadOnly)) { QTextStream dataStream(&dataFile); CatalogContents += dataStream.readAll(); dataFile.close(); return true; } return false; } QString AddCatDialog::writeCatalogHeader() { QString name = (acd->CatalogName->text().isEmpty() ? i18n("Custom") : acd->CatalogName->text()); QString pre = (acd->Prefix->text().isEmpty() ? "CC" : acd->Prefix->text()); char delimiter = (acd->CSVButton->isChecked() ? ',' : ' '); QString h = QString("# Delimiter: %1\n").arg(delimiter); h += QString("# Name: %1\n").arg(name); h += QString("# Prefix: %1\n").arg(pre); h += QString("# Color: %1\n").arg(acd->ColorButton->color().name()); h += QString("# Epoch: %1\n").arg(acd->Epoch->value()); h += QString("# "); for (int i = 0; i < acd->FieldList->count(); ++i) { QString f = acd->FieldList->item(i)->text(); if (f == i18n("ID Number")) { h += "ID "; } else if (f == i18n("Right Ascension")) { h += "RA "; } else if (f == i18n("Declination")) { h += "Dc "; } else if (f == i18n("Object Type")) { h += "Tp "; } else if (f == i18n("Common Name")) { h += "Nm "; } else if (f == i18n("Magnitude")) { h += "Mg "; } else if (f == i18n("Flux")) { h += "Flux "; } else if (f == i18n("Major Axis")) { h += "Mj "; } else if (f == i18n("Minor Axis")) { h += "Mn "; } else if (f == i18n("Position Angle")) { h += "PA "; } else if (f == i18n("Ignore")) { h += "Ig "; } } h += '\n'; return h; } void AddCatDialog::slotShowDataFile() { QFile dataFile(acd->DataURL->url().toLocalFile()); if (!acd->DataURL->url().isEmpty() && dataFile.open(QIODevice::ReadOnly)) { acd->DataFileBox->clear(); QTextStream dataStream(&dataFile); acd->DataFileBox->addItems(dataStream.readAll().split('\n')); dataFile.close(); } } void AddCatDialog::slotPreviewCatalog() { if (validateDataFile()) { - KMessageBox::informationList(0, i18n("Preview of %1", acd->CatalogName->text()), CatalogContents.split('\n'), + KMessageBox::informationList(nullptr, i18n("Preview of %1", acd->CatalogName->text()), CatalogContents.split('\n'), i18n("Catalog Preview")); } } void AddCatDialog::slotCreateCatalog() { if (validateDataFile()) { //CatalogContents now contains the text for the catalog file, //and objList contains the parsed objects //Warn user if file exists! if (QFile::exists(acd->CatalogURL->url().toLocalFile())) { QUrl u(acd->CatalogURL->url()); - int r = KMessageBox::warningContinueCancel(0, + int r = KMessageBox::warningContinueCancel(nullptr, i18n("A file named \"%1\" already exists. " "Overwrite it?", u.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite()); if (r == KMessageBox::Cancel) return; } QFile OutFile(acd->CatalogURL->url().toLocalFile()); if (!OutFile.open(QIODevice::WriteOnly)) { - KMessageBox::sorry(0, i18n("Could not open the file %1 for writing.", acd->CatalogURL->url().toLocalFile()), + KMessageBox::sorry(nullptr, i18n("Could not open the file %1 for writing.", acd->CatalogURL->url().toLocalFile()), i18n("Error Opening Output File")); } else { QTextStream outStream(&OutFile); outStream << CatalogContents; OutFile.close(); KStarsData::Instance()->skyComposite()->addCustomCatalog(OutFile.fileName(), 0); QDialog::accept(); close(); } } } diff --git a/kstars/dialogs/addlinkdialog.cpp b/kstars/dialogs/addlinkdialog.cpp index 7a966d0a1..27812b8d6 100644 --- a/kstars/dialogs/addlinkdialog.cpp +++ b/kstars/dialogs/addlinkdialog.cpp @@ -1,86 +1,86 @@ /*************************************************************************** addlinkdialog.cpp - K Desktop Planetarium ------------------- begin : Sun Oct 21 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 "addlinkdialog.h" #include #include #include #include #include #include "skyobjects/skyobject.h" AddLinkDialogUI::AddLinkDialogUI(QWidget *parent) : QFrame(parent) { setupUi(this); } AddLinkDialog::AddLinkDialog(QWidget *parent, const QString &oname) : QDialog(parent), ObjectName(oname) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif ald = new AddLinkDialogUI(this); setWindowTitle(i18n("Add Custom URL to %1", oname)); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(ald); setLayout(mainLayout); 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())); //connect signals to slots connect(ald->URLButton, SIGNAL(clicked()), this, SLOT(checkURL())); connect(ald->ImageRadio, SIGNAL(toggled(bool)), this, SLOT(changeDefaultDescription(bool))); ald->ImageRadio->setChecked(true); ald->DescBox->setText(i18n("Show image of ") + ObjectName); } void AddLinkDialog::checkURL(void) { QUrl _url(url()); if (_url.isValid()) //Is the string a valid URL? { QDesktopServices::openUrl(_url); //If so, launch the browser to see if it's the correct document } else //If not, print a warning message box that offers to open the browser to a search engine. { QString message = i18n("The URL is not valid. Would you like to open a browser window\nto the Google search engine?"); QString caption = i18n("Invalid URL"); - if (KMessageBox::warningYesNo(0, message, caption, KGuiItem(i18n("Browse Google")), + if (KMessageBox::warningYesNo(nullptr, message, caption, KGuiItem(i18n("Browse Google")), KGuiItem(i18n("Do Not Browse"))) == KMessageBox::Yes) { QDesktopServices::openUrl(QUrl("http://www.google.com")); } } } void AddLinkDialog::changeDefaultDescription(bool imageEnabled) { if (imageEnabled) ald->DescBox->setText(i18n("Show image of ") + ObjectName); else ald->DescBox->setText(i18n("Show webpage about ") + ObjectName); } diff --git a/kstars/dialogs/addlinkdialog.h b/kstars/dialogs/addlinkdialog.h index 85ff82b8a..7b3290296 100644 --- a/kstars/dialogs/addlinkdialog.h +++ b/kstars/dialogs/addlinkdialog.h @@ -1,109 +1,109 @@ /*************************************************************************** addlinQDialog - K Desktop Planetarium ------------------- begin : Sun Oct 21 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. * * * ***************************************************************************/ #ifndef ADDLINKDIALOG_H_ #define ADDLINKDIALOG_H_ #include #include #include #include #include "ui_addlinkdialog.h" class QString; class AddLinkDialogUI : public QFrame, public Ui::AddLinkDialog { Q_OBJECT public: - explicit AddLinkDialogUI(QWidget *parent = 0); + explicit AddLinkDialogUI(QWidget *parent = nullptr); }; /** *@class AddLinkDialog * AddLinkDialog is a simple dialog for adding a custom URL to a popup menu. *@author Jason Harris *@version 1.0 */ class AddLinkDialog : public QDialog { Q_OBJECT public: /** *Constructor. */ - explicit AddLinkDialog(QWidget *parent = 0, const QString &oname = i18n("object")); + explicit AddLinkDialog(QWidget *parent = nullptr, const QString &oname = i18n("object")); /** *Destructor (empty) */ ~AddLinkDialog() override {} /** *@return QString of the entered URL */ QString url() const { return ald->URLBox->text(); } /** *@short Set the URL text *@param s the new URL text */ void setURL(const QString &s) { ald->URLBox->setText(s); } /** *@return QString of the entered menu entry text */ QString desc() const { return ald->DescBox->text(); } /** *@short Set the Description text *@param s the new description text */ void setDesc(const QString &s) { ald->DescBox->setText(s); } /** *@return true if user declared the link is an image */ bool isImageLink() const { return ald->ImageRadio->isChecked(); } /** *@short Set the link type *@param b if true, link is an image link. */ void setImageLink(bool b) { ald->ImageRadio->setChecked(b); } private slots: /** *Open the entered URL in the web browser */ void checkURL(void); /** *We provide a default menu text string; this function changes the *default string if the link type (image/webpage) is changed. Note *that if the user has changed the menu text, this function does nothing. *@param imageEnabled if true, show image string; otherwise show webpage string. */ void changeDefaultDescription(bool imageEnabled); private: QString ObjectName; AddLinkDialogUI *ald; }; #endif diff --git a/kstars/dialogs/detaildialog.cpp b/kstars/dialogs/detaildialog.cpp index 2018eba7d..851d879f8 100644 --- a/kstars/dialogs/detaildialog.cpp +++ b/kstars/dialogs/detaildialog.cpp @@ -1,1335 +1,1335 @@ /*************************************************************************** detaildialog.cpp - description ------------------- begin : Sun May 5 2002 copyright : (C) 2002 by Jason Harris and Jasem Mutlaq 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 "detaildialog.h" #include "config-kstars.h" #include "addlinkdialog.h" #include "kspaths.h" #include "kstars.h" #include "kstarsdata.h" #include "ksutils.h" #include "observinglist.h" #include "skymap.h" #include "thumbnailpicker.h" #include "skycomponents/constellationboundarylines.h" #include "skycomponents/skymapcomposite.h" #include "skyobjects/deepskyobject.h" #include "skyobjects/ksasteroid.h" #include "skyobjects/kscomet.h" #include "skyobjects/ksmoon.h" #include "skyobjects/starobject.h" #include "skyobjects/supernova.h" #include "skycomponents/catalogcomponent.h" #ifdef HAVE_INDI #include #include "indi/indilistener.h" #endif #include #include DetailDialog::DetailDialog(SkyObject *o, const KStarsDateTime &ut, GeoLocation *geo, QWidget *parent) - : KPageDialog(parent), selectedObject(o), Data(0), DataComet(0), Pos(0), Links(0), Adv(0), Log(0) + : KPageDialog(parent), selectedObject(o), Data(nullptr), DataComet(nullptr), Pos(nullptr), Links(nullptr), Adv(nullptr), Log(nullptr) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif setFaceType(Tabbed); setBackgroundRole(QPalette::Base); titlePalette = palette(); titlePalette.setColor(backgroundRole(), palette().color(QPalette::Active, QPalette::Highlight)); titlePalette.setColor(foregroundRole(), palette().color(QPalette::Active, QPalette::HighlightedText)); //Create thumbnail image Thumbnail.reset(new QPixmap(200, 200)); setWindowTitle(i18n("Object Details")); // JM 2016-11-22: Do we really need a close button? //setStandardButtons(QDialogButtonBox::Close); setStandardButtons(QDialogButtonBox::NoButton); createGeneralTab(); createPositionTab(ut, geo); createLinksTab(); createAdvancedTab(); createLogTab(); } DetailDialog::~DetailDialog() { } void DetailDialog::createGeneralTab() { Data = new DataWidget(this); addPage(Data, i18n("General")); Data->Names->setPalette(titlePalette); //Connections connect(Data->ObsListButton, SIGNAL(clicked()), this, SLOT(addToObservingList())); connect(Data->CenterButton, SIGNAL(clicked()), this, SLOT(centerMap())); #ifdef HAVE_INDI connect(Data->ScopeButton, SIGNAL(clicked()), this, SLOT(centerTelescope())); #else Data->ScopeButton->setEnabled(false); #endif connect(Data->Image, SIGNAL(clicked()), this, SLOT(updateThumbnail())); // Stuff that should be visible only for specific types of objects Data->IllumLabel->setVisible(false); // Only shown for the moon Data->Illumination->setVisible(false); Data->BVIndex->setVisible(false); // Only shown for stars Data->BVLabel->setVisible(false); //Show object thumbnail image showThumbnail(); //Fill in the data fields //Contents depend on type of object QString objecttyp, str; switch (selectedObject->type()) { case SkyObject::STAR: { StarObject *s = (StarObject *)selectedObject; if (s->getHDIndex()) { Data->Names->setText(QString("%1, HD %2").arg(s->longname()).arg(s->getHDIndex())); } else { Data->Names->setText(s->longname()); } objecttyp = s->sptype() + ' ' + i18n("star"); Data->Magnitude->setText(i18nc("number in magnitudes", "%1 mag", QLocale().toString(s->mag(), 'f', 2))); //show to hundredth place Data->BVLabel->setVisible(true); Data->BVIndex->setVisible(true); if (s->getBVIndex() < 30.) { Data->BVIndex->setText(QString::number(s->getBVIndex(), 'f', 2)); } //The thumbnail image is empty, and isn't clickable for stars //Also, don't show the border around the Image QFrame. Data->Image->setFrameStyle(QFrame::NoFrame); disconnect(Data->Image, SIGNAL(clicked()), this, SLOT(updateThumbnail())); //distance if (s->distance() > 2000. || s->distance() < 0.) // parallax < 0.5 mas { Data->Distance->setText(QString(i18nc("larger than 2000 parsecs", "> 2000 pc"))); } else if (s->distance() > 50.) //show to nearest integer { Data->Distance->setText(i18nc("number in parsecs", "%1 pc", QLocale().toString(s->distance(), 'f', 0))); } else if (s->distance() > 10.0) //show to tenths place { Data->Distance->setText(i18nc("number in parsecs", "%1 pc", QLocale().toString(s->distance(), 'f', 1))); } else //show to hundredths place { Data->Distance->setText(i18nc("number in parsecs", "%1 pc", QLocale().toString(s->distance(), 'f', 2))); } //Note multiplicity/variablility in angular size label Data->AngSizeLabel->setText(QString()); Data->AngSize->setText(QString()); Data->AngSizeLabel->setFont(Data->AngSize->font()); if (s->isMultiple() && s->isVariable()) { Data->AngSizeLabel->setText(i18nc("the star is a multiple star", "multiple") + ','); Data->AngSize->setText(i18nc("the star is a variable star", "variable")); } else if (s->isMultiple()) { Data->AngSizeLabel->setText(i18nc("the star is a multiple star", "multiple")); } else if (s->isVariable()) { Data->AngSizeLabel->setText(i18nc("the star is a variable star", "variable")); } break; //end of stars case } case SkyObject::ASTEROID: //[fall through to planets] case SkyObject::COMET: //[fall through to planets] case SkyObject::MOON: //[fall through to planets] case SkyObject::PLANET: { KSPlanetBase *ps = (KSPlanetBase *)selectedObject; Data->Names->setText(ps->longname()); //Type is "G5 star" for Sun if (ps->name() == "Sun") { objecttyp = i18n("G5 star"); } else if (ps->name() == "Moon") { objecttyp = ps->translatedName(); } else if (ps->name() == i18n("Pluto") || ps->name() == "Ceres" || ps->name() == "Eris") // TODO: Check if Ceres / Eris have translations and i18n() them { objecttyp = i18n("Dwarf planet"); } else { objecttyp = ps->typeName(); } //The moon displays illumination fraction and updateMag is called to calculate moon's current magnitude if (selectedObject->name() == "Moon") { Data->IllumLabel->setVisible(true); Data->Illumination->setVisible(true); Data->Illumination->setText( QString("%1 %").arg(QLocale().toString(((KSMoon *)selectedObject)->illum() * 100., 'f', 0))); ((KSMoon *)selectedObject)->updateMag(); } // JM: Shouldn't we use the calculated magnitude? Disabling the following /* if(selectedObject->type() == SkyObject::COMET){ Data->Magnitude->setText(i18nc("number in magnitudes", "%1 mag", QLocale().toString( ((KSComet *)selectedObject)->getTotalMagnitudeParameter(), 'f', 2))); //show to hundredth place } else{*/ Data->Magnitude->setText(i18nc("number in magnitudes", "%1 mag", QLocale().toString(ps->mag(), 'f', 2))); //show to hundredth place //} //Distance from Earth. The moon requires a unit conversion if (ps->name() == "Moon") { Data->Distance->setText( i18nc("distance in kilometers", "%1 km", QLocale().toString(ps->rearth() * AU_KM, 'f', 2))); } else { Data->Distance->setText( i18nc("distance in Astronomical Units", "%1 AU", QLocale().toString(ps->rearth(), 'f', 3))); } //Angular size; moon and sun in arcmin, others in arcsec if (ps->angSize()) { if (ps->name() == "Sun" || ps->name() == "Moon") { Data->AngSize->setText(i18nc( "angular size in arcminutes", "%1 arcmin", QLocale().toString( ps->angSize(), 'f', 1))); // Needn't be a plural form because sun / moon will never contract to 1 arcminute } else { Data->AngSize->setText(i18nc("angular size in arcseconds", "%1 arcsec", QLocale().toString(ps->angSize() * 60.0, 'f', 1))); } } else { Data->AngSize->setText("--"); } break; //end of planets/comets/asteroids case } case SkyObject::SUPERNOVA: { Supernova *sup = (Supernova *)selectedObject; objecttyp = i18n("Supernova"); Data->Names->setText(sup->name()); if (sup->mag() < 99) Data->Magnitude->setText( i18nc("number in magnitudes", "%1 mag", QLocale().toString(sup->mag(), 'f', 2))); else Data->Magnitude->setText("--"); Data->DistanceLabel->setVisible(false); Data->Distance->setVisible(false); Data->AngSizeLabel->setVisible(false); Data->AngSize->setVisible(false); QLabel *discoveryDateLabel = new QLabel(i18n("Discovery Date:"), this); QLabel *discoveryDate = new QLabel(sup->getDate(), this); Data->dataGridLayout->addWidget(discoveryDateLabel, 1, 0); Data->dataGridLayout->addWidget(discoveryDate, 1, 1); QLabel *typeLabel = new QLabel(i18n("Type:"), this); QLabel *type = new QLabel(sup->getType(), this); Data->dataGridLayout->addWidget(typeLabel, 2, 0); Data->dataGridLayout->addWidget(type, 2, 1); QLabel *hostGalaxyLabel = new QLabel(i18n("Host Galaxy:"), this); QLabel *hostGalaxy = new QLabel(sup->getHostGalaxy().isEmpty() ? "--" : sup->getHostGalaxy(), this); Data->dataGridLayout->addWidget(hostGalaxyLabel, 3, 0); Data->dataGridLayout->addWidget(hostGalaxy, 3, 1); QLabel *redShiftLabel = new QLabel(i18n("Red Shift:"), this); QLabel *redShift = new QLabel( (sup->getRedShift() < 99) ? QString::number(sup->getRedShift(), 'f', 2) : QString("--"), this); Data->dataGridLayout->addWidget(redShiftLabel, 4, 0); Data->dataGridLayout->addWidget(redShift, 4, 1); break; } default: //deep-sky objects { DeepSkyObject *dso = (DeepSkyObject *)selectedObject; //Show all names recorded for the object QStringList nameList; if (!dso->longname().isEmpty() && dso->longname() != dso->name()) { nameList.append(dso->translatedLongName()); nameList.append(dso->translatedName()); } else { nameList.append(dso->translatedName()); } if (!dso->translatedName2().isEmpty()) { nameList.append(dso->translatedName2()); } if (dso->ugc() != 0) { nameList.append(QString("UGC %1").arg(dso->ugc())); } if (dso->pgc() != 0) { nameList.append(QString("PGC %1").arg(dso->pgc())); } Data->Names->setText(nameList.join(",")); objecttyp = dso->typeName(); if (dso->type() == SkyObject::RADIO_SOURCE) { Q_ASSERT(dso->customCatalog()); // the in-built catalogs don't have radio sources Data->MagLabel->setText( i18nc("integrated flux at a frequency", "Flux(%1):", dso->customCatalog()->fluxFrequency())); Data->Magnitude->setText(i18nc("integrated flux value", "%1 %2", QLocale().toString(dso->flux(), 'f', 1), dso->customCatalog()->fluxUnit())); //show to tenths place } else if (dso->mag() > 90.0) { Data->Magnitude->setText("--"); } else { Data->Magnitude->setText(i18nc("number in magnitudes", "%1 mag", QLocale().toString(dso->mag(), 'f', 1))); //show to tenths place } //No distances at this point... Data->Distance->setText("--"); //Only show decimal place for small angular sizes if (dso->a() > 10.0) { Data->AngSize->setText( i18nc("angular size in arcminutes", "%1 arcmin", QLocale().toString(dso->a(), 'f', 0))); } else if (dso->a()) { Data->AngSize->setText( i18nc("angular size in arcminutes", "%1 arcmin", QLocale().toString(dso->a(), 'f', 1))); } else { Data->AngSize->setText("--"); } break; } } // Add specifics data switch (selectedObject->type()) { case SkyObject::ASTEROID: { KSAsteroid *ast = (KSAsteroid *)selectedObject; DataComet = new DataCometWidget(this); // Show same specifics data as comets Data->IncludeData->layout()->addWidget(DataComet); // Perihelion str.setNum(ast->getPerihelion()); DataComet->Perihelion->setText(str + " AU"); // Earth MOID if (ast->getEarthMOID() == 0) str = ""; else str.setNum(ast->getEarthMOID()).append(" AU"); DataComet->EarthMOID->setText(str); // Orbit ID DataComet->OrbitID->setText(ast->getOrbitID()); // Orbit Class DataComet->OrbitClass->setText(ast->getOrbitClass()); // NEO if (ast->isNEO()) DataComet->NEO->setText("Yes"); else DataComet->NEO->setText("No"); // Albedo if (ast->getAlbedo() == 0.0) str = ""; else str.setNum(ast->getAlbedo()); DataComet->Albedo->setText(str); // Diameter if (ast->getDiameter() == 0.0) str = ""; else str.setNum(ast->getDiameter()).append(" km"); DataComet->Diameter->setText(str); // Dimensions if (ast->getDimensions().isEmpty()) DataComet->Dimensions->setText(""); else DataComet->Dimensions->setText(ast->getDimensions() + " km"); // Rotation period if (ast->getRotationPeriod() == 0.0) str = ""; else str.setNum(ast->getRotationPeriod()).append(" h"); DataComet->Rotation->setText(str); // Period if (ast->getPeriod() == 0.0) str = ""; else str.setNum(ast->getPeriod()).append(" y"); DataComet->Period->setText(str); break; } case SkyObject::COMET: { KSComet *com = (KSComet *)selectedObject; DataComet = new DataCometWidget(this); Data->IncludeData->layout()->addWidget(DataComet); // Perihelion str.setNum(com->getPerihelion()); DataComet->Perihelion->setText(str + " AU"); // Earth MOID if (com->getEarthMOID() == 0) str = ""; else str.setNum(com->getEarthMOID()).append(" AU"); DataComet->EarthMOID->setText(str); // Orbit ID DataComet->OrbitID->setText(com->getOrbitID()); // Orbit Class DataComet->OrbitClass->setText(com->getOrbitClass()); // NEO if (com->isNEO()) DataComet->NEO->setText("Yes"); else DataComet->NEO->setText("No"); // Albedo if (com->getAlbedo() == 0.0) str = ""; else str.setNum(com->getAlbedo()); DataComet->Albedo->setText(str); // Diameter if (com->getDiameter() == 0.0) str = ""; else str.setNum(com->getDiameter()).append(" km"); DataComet->Diameter->setText(str); // Dimensions if (com->getDimensions().isEmpty()) DataComet->Dimensions->setText(""); else DataComet->Dimensions->setText(com->getDimensions() + " km"); // Rotation period if (com->getRotationPeriod() == 0.0) str = ""; else str.setNum(com->getRotationPeriod()).append(" h"); DataComet->Rotation->setText(str); // Period if (com->getPeriod() == 0.0) str = ""; else str.setNum(com->getPeriod()).append(" y"); DataComet->Period->setText(str); break; } } //Common to all types: QString cname = KStarsData::Instance()->skyComposite()->constellationBoundary()->constellationName(selectedObject); if (selectedObject->type() != SkyObject::CONSTELLATION) { cname = i18nc("%1 type of sky object (planet, asteroid etc), %2 name of a constellation", "%1 in %2", objecttyp, cname); } Data->ObjectTypeInConstellation->setText(cname); } void DetailDialog::createPositionTab(const KStarsDateTime &ut, GeoLocation *geo) { Pos = new PositionWidget(this); addPage(Pos, i18n("Position")); Pos->CoordTitle->setPalette(titlePalette); Pos->RSTTitle->setPalette(titlePalette); KStarsData *data = KStarsData::Instance(); //Coordinates Section: //Don't use KLocale::formatNumber() for the epoch string, //because we don't want a thousands-place separator! selectedObject->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false); QString sEpoch = QString::number(KStarsDateTime::jdToEpoch(selectedObject->getLastPrecessJD()), 'f', 1); //Replace the decimal point with localized decimal symbol sEpoch.replace('.', QLocale().decimalPoint()); // Is this necessary? -- asimha Oct 2016 qDebug() << (selectedObject->deprecess(data->updateNum())).ra0().toHMSString() << (selectedObject->deprecess(data->updateNum())).dec0().toDMSString() << endl; //qDebug() << selectedObject->ra().toHMSString() << selectedObject->dec().toDMSString() << endl; Pos->RALabel->setText(i18n("RA (%1):", sEpoch)); Pos->DecLabel->setText(i18n("DE (%1):", sEpoch)); Pos->RA->setText(selectedObject->ra().toHMSString()); Pos->Dec->setText(selectedObject->dec().toDMSString()); selectedObject->EquatorialToHorizontal(data->lst(), data->geo()->lat()); Pos->Az->setText(selectedObject->az().toDMSString()); dms a; if (Options::useAltAz()) a = selectedObject->alt(); else a = selectedObject->altRefracted(); Pos->Alt->setText(a.toDMSString()); // Display the RA0 and Dec0 for objects that are outside the solar system if (!selectedObject->isSolarSystem()) { Pos->RA0->setText(selectedObject->ra0().toHMSString()); Pos->Dec0->setText(selectedObject->dec0().toDMSString()); } else { Pos->RA0->setText("--"); Pos->Dec0->setText("--"); } //Hour Angle can be negative, but dms HMS expressions cannot. //Here's a kludgy workaround: dms lst = geo->GSTtoLST(ut.gst()); dms ha(lst.Degrees() - selectedObject->ra().Degrees()); QChar sgn('+'); if (ha.Hours() > 12.0) { ha.setH(24.0 - ha.Hours()); sgn = '-'; } Pos->HA->setText(QString("%1%2").arg(sgn).arg(ha.toHMSString())); //Airmass is approximated as the secant of the zenith distance, //equivalent to 1./sin(Alt). Beware of Inf at Alt=0! if (selectedObject->alt().Degrees() > 0.0) Pos->Airmass->setText(QLocale().toString(selectedObject->airmass(), 'f', 2)); else Pos->Airmass->setText("--"); //Rise/Set/Transit Section: //Prepare time/position variables QTime rt = selectedObject->riseSetTime(ut, geo, true); //true = use rise time dms raz = selectedObject->riseSetTimeAz(ut, geo, true); //true = use rise time //If transit time is before rise time, use transit time for tomorrow QTime tt = selectedObject->transitTime(ut, geo); dms talt = selectedObject->transitAltitude(ut, geo); if (tt < rt) { tt = selectedObject->transitTime(ut.addDays(1), geo); talt = selectedObject->transitAltitude(ut.addDays(1), geo); } //If set time is before rise time, use set time for tomorrow QTime st = selectedObject->riseSetTime(ut, geo, false); //false = use set time dms saz = selectedObject->riseSetTimeAz(ut, geo, false); //false = use set time if (st < rt) { st = selectedObject->riseSetTime(ut.addDays(1), geo, false); //false = use set time saz = selectedObject->riseSetTimeAz(ut.addDays(1), geo, false); //false = use set time } if (rt.isValid()) { Pos->TimeRise->setText(QString().sprintf("%02d:%02d", rt.hour(), rt.minute())); Pos->TimeSet->setText(QString().sprintf("%02d:%02d", st.hour(), st.minute())); Pos->AzRise->setText(raz.toDMSString()); Pos->AzSet->setText(saz.toDMSString()); } else { if (selectedObject->alt().Degrees() > 0.0) { Pos->TimeRise->setText(i18n("Circumpolar")); Pos->TimeSet->setText(i18n("Circumpolar")); } else { Pos->TimeRise->setText(i18n("Never rises")); Pos->TimeSet->setText(i18n("Never rises")); } Pos->AzRise->setText(i18nc("Not Applicable", "N/A")); Pos->AzSet->setText(i18nc("Not Applicable", "N/A")); } Pos->TimeTransit->setText(QString().sprintf("%02d:%02d", tt.hour(), tt.minute())); Pos->AltTransit->setText(talt.toDMSString()); // Restore the position and other time-dependent parameters selectedObject->recomputeCoords(ut, geo); } void DetailDialog::createLinksTab() { // don't create a link tab for an unnamed star if (selectedObject->name() == QString("star")) return; Links = new LinksWidget(this); addPage(Links, i18n("Links")); Links->InfoTitle->setPalette(titlePalette); Links->ImagesTitle->setPalette(titlePalette); foreach (const QString &s, selectedObject->InfoTitle()) Links->InfoTitleList->addItem(i18nc("Image/info menu item (should be translated)", s.toLocal8Bit())); //Links->InfoTitleList->setCurrentRow(0); foreach (const QString &s, selectedObject->ImageTitle()) Links->ImageTitleList->addItem(i18nc("Image/info menu item (should be translated)", s.toLocal8Bit())); // Signals/Slots connect(Links->ViewButton, SIGNAL(clicked()), this, SLOT(viewLink())); connect(Links->AddLinkButton, SIGNAL(clicked()), this, SLOT(addLink())); connect(Links->EditLinkButton, SIGNAL(clicked()), this, SLOT(editLinkDialog())); connect(Links->RemoveLinkButton, SIGNAL(clicked()), this, SLOT(removeLinkDialog())); // When an item is selected in info list, selected items are cleared image list. connect(Links->InfoTitleList, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(setCurrentLink(QListWidgetItem*))); connect(Links->InfoTitleList, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), Links->ImageTitleList, SLOT(clearSelection())); // vice versa connect(Links->ImageTitleList, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(setCurrentLink(QListWidgetItem*))); connect(Links->ImageTitleList, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), Links->InfoTitleList, SLOT(clearSelection())); connect(Links->InfoTitleList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(viewLink())); connect(Links->ImageTitleList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(viewLink())); connect(Links->InfoTitleList, SIGNAL(itemSelectionChanged()), this, SLOT(updateButtons())); connect(Links->ImageTitleList, SIGNAL(itemSelectionChanged()), this, SLOT(updateButtons())); updateLists(); } void DetailDialog::addLink() { if (!selectedObject) return; QPointer adialog = new AddLinkDialog(this, selectedObject->name()); QString entry; QFile file; if (adialog->exec() == QDialog::Accepted) { if (adialog->isImageLink()) { //Add link to object's ImageList, and descriptive text to its ImageTitle list selectedObject->ImageList().append(adialog->url()); selectedObject->ImageTitle().append(adialog->desc()); //Also, update the user's custom image links database //check for user's image-links database. If it doesn't exist, create it. file.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "image_url.dat"); //determine filename in local user KDE directory tree. if (!file.open(QIODevice::ReadWrite | QIODevice::Append)) { QString message = i18n("Custom image-links file could not be opened.\nLink cannot be recorded for future sessions."); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); delete adialog; return; } else { entry = selectedObject->name() + ':' + adialog->desc() + ':' + adialog->url(); QTextStream stream(&file); stream << entry << endl; file.close(); updateLists(); } } else { selectedObject->InfoList().append(adialog->url()); selectedObject->InfoTitle().append(adialog->desc()); //check for user's image-links database. If it doesn't exist, create it. file.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "info_url.dat"); //determine filename in local user KDE directory tree. if (!file.open(QIODevice::ReadWrite | QIODevice::Append)) { QString message = i18n( "Custom information-links file could not be opened.\nLink cannot be recorded for future sessions."); - KMessageBox::sorry(0, message, i18n("Could not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could not Open File")); delete adialog; return; } else { entry = selectedObject->name() + ':' + adialog->desc() + ':' + adialog->url(); QTextStream stream(&file); stream << entry << endl; file.close(); updateLists(); } } } delete adialog; } void DetailDialog::createAdvancedTab() { // Don't create an adv tab for an unnamed star or if advinterface file failed loading // We also don't need adv dialog for solar system objects. if (selectedObject->name() == QString("star") || KStarsData::Instance()->avdTree().isEmpty() || selectedObject->type() == SkyObject::PLANET || selectedObject->type() == SkyObject::COMET || selectedObject->type() == SkyObject::ASTEROID) return; Adv = new DatabaseWidget(this); addPage(Adv, i18n("Advanced")); connect(Adv->ADVTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(viewADVData())); populateADVTree(); } void DetailDialog::createLogTab() { //Don't create a log tab for an unnamed star if (selectedObject->name() == QString("star")) return; // Log Tab Log = new LogWidget(this); addPage(Log, i18n("Log")); Log->LogTitle->setPalette(titlePalette); if (selectedObject->userLog().isEmpty()) Log->UserLog->setText( i18n("Record here observation logs and/or data on %1.", selectedObject->translatedName())); else Log->UserLog->setText(selectedObject->userLog()); //Automatically save the log contents when the widget loses focus connect(Log->UserLog, SIGNAL(focusOut()), this, SLOT(saveLogData())); } void DetailDialog::setCurrentLink(QListWidgetItem *it) { m_CurrentLink = it; } void DetailDialog::viewLink() { QString URL; if (m_CurrentLink == nullptr) return; if (m_CurrentLink->listWidget() == Links->InfoTitleList) { URL = QString(selectedObject->InfoList().at(Links->InfoTitleList->row(m_CurrentLink))); } else if (m_CurrentLink->listWidget() == Links->ImageTitleList) { URL = QString(selectedObject->ImageList().at(Links->ImageTitleList->row(m_CurrentLink))); } if (!URL.isEmpty()) QDesktopServices::openUrl(QUrl(URL)); } void DetailDialog::updateLists() { Links->InfoTitleList->clear(); Links->ImageTitleList->clear(); foreach (const QString &s, selectedObject->InfoTitle()) Links->InfoTitleList->addItem(s); foreach (const QString &s, selectedObject->ImageTitle()) Links->ImageTitleList->addItem(s); updateButtons(); } void DetailDialog::updateButtons() { bool anyLink = false; if (!Links->InfoTitleList->selectedItems().isEmpty() || !Links->ImageTitleList->selectedItems().isEmpty()) anyLink = true; // Buttons could be disabled if lists are initially empty, we enable and disable them here // depending on the current status of the list. Links->ViewButton->setEnabled(anyLink); Links->EditLinkButton->setEnabled(anyLink); Links->RemoveLinkButton->setEnabled(anyLink); } void DetailDialog::editLinkDialog() { int type = 0, row = 0; QString search_line, replace_line, currentItemTitle, currentItemURL; if (m_CurrentLink == nullptr) return; QDialog editDialog(this); editDialog.setWindowTitle(i18n("Edit Link")); QVBoxLayout *mainLayout = new QVBoxLayout; QFrame editFrame(&editDialog); mainLayout->addWidget(&editFrame); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(accepted()), &editDialog, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), &editDialog, SLOT(reject())); editDialog.setLayout(mainLayout); if (m_CurrentLink->listWidget() == Links->InfoTitleList) { row = Links->InfoTitleList->row(m_CurrentLink); currentItemTitle = m_CurrentLink->text(); currentItemURL = selectedObject->InfoList().at(row); search_line = selectedObject->name(); search_line += ':'; search_line += currentItemTitle; search_line += ':'; search_line += currentItemURL; type = 0; } else if (m_CurrentLink->listWidget() == Links->ImageTitleList) { row = Links->ImageTitleList->row(m_CurrentLink); currentItemTitle = m_CurrentLink->text(); currentItemURL = selectedObject->ImageList().at(row); search_line = selectedObject->name(); search_line += ':'; search_line += currentItemTitle; search_line += ':'; search_line += currentItemURL; type = 1; } else return; QLineEdit editNameField(&editFrame); editNameField.setObjectName("nameedit"); editNameField.home(false); editNameField.setText(currentItemTitle); QLabel editLinkURL(i18n("URL:"), &editFrame); QLineEdit editLinkField(&editFrame); editLinkField.setObjectName("urledit"); editLinkField.home(false); editLinkField.setText(currentItemURL); QVBoxLayout vlay(&editFrame); vlay.setObjectName("vlay"); QHBoxLayout editLinkLayout(&editFrame); editLinkLayout.setObjectName("editlinklayout"); editLinkLayout.addWidget(&editLinkURL); editLinkLayout.addWidget(&editLinkField); vlay.addWidget(&editNameField); vlay.addLayout(&editLinkLayout); bool go(true); // If user presses cancel then skip the action if (editDialog.exec() != QDialog::Accepted) go = false; // If nothing changed, skip th action if (editLinkField.text() == currentItemURL && editNameField.text() == currentItemTitle) go = false; if (go) { replace_line = selectedObject->name() + ':' + editNameField.text() + ':' + editLinkField.text(); // Info Link if (type == 0) { selectedObject->InfoTitle().replace(row, editNameField.text()); selectedObject->InfoList().replace(row, editLinkField.text()); // Image Links } else { selectedObject->ImageTitle().replace(row, editNameField.text()); selectedObject->ImageList().replace(row, editLinkField.text()); } // Update local files updateLocalDatabase(type, search_line, replace_line); // Set focus to the same item again if (type == 0) Links->InfoTitleList->setCurrentRow(row); else Links->ImageTitleList->setCurrentRow(row); } } void DetailDialog::removeLinkDialog() { int type = 0, row = 0; QString currentItemURL, currentItemTitle, LineEntry, TempFileName; QFile URLFile; QTemporaryFile TempFile; TempFile.setAutoRemove(false); TempFile.open(); TempFileName = TempFile.fileName(); if (m_CurrentLink == nullptr) return; if (m_CurrentLink->listWidget() == Links->InfoTitleList) { row = Links->InfoTitleList->row(m_CurrentLink); currentItemTitle = m_CurrentLink->text(); currentItemURL = selectedObject->InfoList()[row]; LineEntry = selectedObject->name(); LineEntry += ':'; LineEntry += currentItemTitle; LineEntry += ':'; LineEntry += currentItemURL; type = 0; } else if (m_CurrentLink->listWidget() == Links->ImageTitleList) { row = Links->ImageTitleList->row(m_CurrentLink); currentItemTitle = m_CurrentLink->text(); currentItemURL = selectedObject->ImageList()[row]; LineEntry = selectedObject->name(); LineEntry += ':'; LineEntry += currentItemTitle; LineEntry += ':'; LineEntry += currentItemURL; type = 1; } else return; - if (KMessageBox::warningContinueCancel(0, i18n("Are you sure you want to remove the %1 link?", currentItemTitle), + if (KMessageBox::warningContinueCancel(nullptr, i18n("Are you sure you want to remove the %1 link?", currentItemTitle), i18n("Delete Confirmation"), KStandardGuiItem::del()) != KMessageBox::Continue) return; if (type == 0) { selectedObject->InfoTitle().removeAt(row); selectedObject->InfoList().removeAt(row); } else { selectedObject->ImageTitle().removeAt(row); selectedObject->ImageList().removeAt(row); } // Remove link from file updateLocalDatabase(type, LineEntry); // Set focus to the 1st item in the list if (type == 0) Links->InfoTitleList->clearSelection(); else Links->ImageTitleList->clearSelection(); } void DetailDialog::updateLocalDatabase(int type, const QString &search_line, const QString &replace_line) { QString TempFileName, file_line; QFile URLFile; QTemporaryFile TempFile; TempFile.setAutoRemove(false); TempFile.open(); QTextStream *temp_stream = nullptr; QTextStream *out_stream = nullptr; bool replace = !replace_line.isEmpty(); if (search_line.isEmpty()) return; TempFileName = TempFile.fileName(); switch (type) { // Info Links case 0: // Get name for our local info_url file URLFile.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "info_url.dat"); break; // Image Links case 1: // Get name for our local info_url file URLFile.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "image_url.dat"); break; } // Copy URL file to temp file KIO::file_copy(QUrl::fromLocalFile(URLFile.fileName()), QUrl::fromLocalFile(TempFileName), -1, KIO::Overwrite | KIO::HideProgressInfo); if (!URLFile.open(QIODevice::WriteOnly)) { qDebug() << "DetailDialog: Failed to open " << URLFile.fileName(); qDebug() << "KStars cannot save to user database"; return; } // Get streams; temp_stream = new QTextStream(&TempFile); out_stream = new QTextStream(&URLFile); while (!temp_stream->atEnd()) { file_line = temp_stream->readLine(); // If we find a match, either replace, or remove (by skipping). if (file_line == search_line) { if (replace) (*out_stream) << replace_line << endl; else continue; } else (*out_stream) << file_line << endl; } URLFile.close(); delete (temp_stream); delete (out_stream); updateLists(); } void DetailDialog::populateADVTree() { QTreeWidgetItem *parent = nullptr; QTreeWidgetItem *temp = nullptr; // We populate the tree iterativley, keeping track of parents as we go // This solution is more efficient than the previous recursion algorithm. foreach (ADVTreeData *item, KStarsData::Instance()->avdTree()) { switch (item->Type) { // Top Level case 0: temp = new QTreeWidgetItem(parent, QStringList(item->Name)); if (parent == nullptr) Adv->ADVTree->addTopLevelItem(temp); parent = temp; break; // End of top level case 1: if (parent != nullptr) parent = parent->parent(); break; // Leaf case 2: new QTreeWidgetItem(parent, QStringList(item->Name)); break; } } } void DetailDialog::viewADVData() { QString link; QTreeWidgetItem *current = Adv->ADVTree->currentItem(); //If the item has children or is invalid, do nothing if (!current || current->childCount() > 0) return; foreach (ADVTreeData *item, KStarsData::Instance()->avdTree()) { if (item->Name == current->text(0)) { link = item->Link; link = parseADVData(link); QDesktopServices::openUrl(QUrl(link)); return; } } } QString DetailDialog::parseADVData(const QString &inlink) { QString link = inlink; QString subLink; int index; if ((index = link.indexOf("KSOBJ")) != -1) { link.remove(index, 5); link = link.insert(index, selectedObject->name()); } if ((index = link.indexOf("KSRA")) != -1) { link.remove(index, 4); subLink.sprintf("%02d%02d%02d", selectedObject->ra0().hour(), selectedObject->ra0().minute(), selectedObject->ra0().second()); subLink = subLink.insert(2, "%20"); subLink = subLink.insert(7, "%20"); link = link.insert(index, subLink); } if ((index = link.indexOf("KSDEC")) != -1) { link.remove(index, 5); if (selectedObject->dec().degree() < 0) { subLink.sprintf("%03d%02d%02d", selectedObject->dec0().degree(), selectedObject->dec0().arcmin(), selectedObject->dec0().arcsec()); subLink = subLink.insert(3, "%20"); subLink = subLink.insert(8, "%20"); } else { subLink.sprintf("%02d%02d%02d", selectedObject->dec0().degree(), selectedObject->dec0().arcmin(), selectedObject->dec0().arcsec()); subLink = subLink.insert(0, "%2B"); subLink = subLink.insert(5, "%20"); subLink = subLink.insert(10, "%20"); } link = link.insert(index, subLink); } return link; } void DetailDialog::saveLogData() { selectedObject->saveUserLog(Log->UserLog->toPlainText()); } void DetailDialog::addToObservingList() { KStarsData::Instance()->observingList()->slotAddObject(selectedObject); } void DetailDialog::centerMap() { SkyMap::Instance()->setClickedObject(selectedObject); SkyMap::Instance()->slotCenter(); } void DetailDialog::centerTelescope() { #ifdef HAVE_INDI if (INDIListener::Instance()->size() == 0) { KMessageBox::sorry(0, i18n("KStars did not find any active telescopes.")); return; } foreach (ISD::GDInterface *gd, INDIListener::Instance()->getDevices()) { INDI::BaseDevice *bd = gd->getBaseDevice(); if (gd->getType() != KSTARS_TELESCOPE) continue; if (bd == nullptr) continue; if (bd->isConnected() == false) { KMessageBox::error(0, i18n("Telescope %1 is offline. Please connect and retry again.", gd->getDeviceName())); return; } ISD::GDSetCommand SlewCMD(INDI_SWITCH, "ON_COORD_SET", "TRACK", ISS_ON, this); gd->setProperty(&SlewCMD); gd->runCommand(INDI_SEND_COORDS, selectedObject); return; } KMessageBox::sorry(0, i18n("KStars did not find any active telescopes.")); #endif } void DetailDialog::showThumbnail() { //No image if object is a star if (selectedObject->type() == SkyObject::STAR || selectedObject->type() == SkyObject::CATALOG_STAR) { Thumbnail->scaled(Data->Image->width(), Data->Image->height()); Thumbnail->fill(Data->DataFrame->palette().color(QPalette::Window)); Data->Image->setPixmap(*Thumbnail); return; } //Try to load the object's image from disk //If no image found, load "no image" image QFile file; QString fname = "thumb-" + selectedObject->name().toLower().remove(' ') + ".png"; if (KSUtils::openDataFile(file, fname)) { file.close(); Thumbnail->load(file.fileName(), "PNG"); } else Thumbnail->load(":/images/noimage.png"); *Thumbnail = Thumbnail->scaled(Data->Image->width(), Data->Image->height(), Qt::KeepAspectRatio, Qt::FastTransformation); Data->Image->setPixmap(*Thumbnail); } void DetailDialog::updateThumbnail() { QPointer tp = new ThumbnailPicker(selectedObject, *Thumbnail, this); if (tp->exec() == QDialog::Accepted) { QString fname = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "thumb-" + selectedObject->name().toLower().remove(' ') + ".png"; Data->Image->setPixmap(*(tp->image())); //If a real image was set, save it. //If the image was unset, delete the old image on disk. if (tp->imageFound()) { bool rc = Data->Image->pixmap()->save(fname, "PNG"); if (rc == false) { - KMessageBox::error(0, i18n("Error: Unable to save image to %1", fname), i18n("Save Thumbnail")); + KMessageBox::error(nullptr, i18n("Error: Unable to save image to %1", fname), i18n("Save Thumbnail")); } else *Thumbnail = *(Data->Image->pixmap()); } else { QFile f; f.setFileName(fname); f.remove(); } } delete tp; } DataWidget::DataWidget(QWidget *p) : QFrame(p) { setupUi(this); DataFrame->setBackgroundRole(QPalette::Base); } DataCometWidget::DataCometWidget(QWidget *p) : QFrame(p) { setupUi(this); } PositionWidget::PositionWidget(QWidget *p) : QFrame(p) { setupUi(this); CoordFrame->setBackgroundRole(QPalette::Base); RSTFrame->setBackgroundRole(QPalette::Base); } LinksWidget::LinksWidget(QWidget *p) : QFrame(p) { setupUi(this); } DatabaseWidget::DatabaseWidget(QWidget *p) : QFrame(p) { setupUi(this); } LogWidget::LogWidget(QWidget *p) : QFrame(p) { setupUi(this); } diff --git a/kstars/dialogs/detaildialog.h b/kstars/dialogs/detaildialog.h index 09feba35d..c094557e5 100644 --- a/kstars/dialogs/detaildialog.h +++ b/kstars/dialogs/detaildialog.h @@ -1,256 +1,256 @@ /*************************************************************************** detaildialog.h - description ------------------- begin : Sun May 5 2002 copyright : (C) 2002 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. * * * ***************************************************************************/ #pragma once #include "ui_details_data.h" #include "ui_details_data_comet.h" #include "ui_details_database.h" #include "ui_details_links.h" #include "ui_details_log.h" #include "ui_details_position.h" #include #include #include #include class QListWidgetItem; class QPixmap; class DataCometWidget; class DataWidget; class GeoLocation; class KStars; class KStarsDateTime; class SkyObject; class PositionWidget; class LinksWidget; class DatabaseWidget; class LogWidget; struct ADVTreeData { QString Name; QString Link; int Type; }; /** * @class DetailDialog * DetailDialog is a window showing detailed information for a selected object. * The window is split into four Tabs: General, Links, Advanced and Log. * The General Tab displays some type-specific data about the object, as well as its * present coordinates and Rise/Set/Transit times for the current date. The Type-specific * data are: * @li Stars: common name, genetive name, Spectral type, magnitude, distance * @li Solar System: name, object type (planet/comet/asteroid), Distance, magnitude (TBD), * angular size (TBD) * @li Deep Sky: Common name, other names, object type, magnitude, angular size * * The Links Tab allows the user to manage the list of Image and Information links * listed in the object's popup menu. The Advanced Tab allows the user to query * a number of professional-grade online astronomical databases for data on the object. * The Log tab allows the user to attach their own text notes about the object. * * The General Tab includes a clickable image of the object. Clicking the image opens * a Thumbnail picker tool, which downloads a list of mages of the object from the * network, which the user may select as the new image for this objects Details window. * * @author Jason Harris, Jasem Mutlaq * @version 1.0 */ class DetailDialog : public KPageDialog { Q_OBJECT public: /** Constructor */ - DetailDialog(SkyObject *o, const KStarsDateTime &ut, GeoLocation *geo, QWidget *parent = 0); + DetailDialog(SkyObject *o, const KStarsDateTime &ut, GeoLocation *geo, QWidget *parent = nullptr); /** Destructor */ ~DetailDialog() override; /** @return pointer to the QPixmap of the object's thumbnail image */ inline QPixmap *thumbnail() { return Thumbnail.get(); } public slots: /** @short Slot to add this object to the observing list. */ void addToObservingList(); /** @short Slot to center this object in the display. */ void centerMap(); /** @short Slot to center this object in the telescope. */ void centerTelescope(); //TODO: showThumbnail() is only called in the ctor; make it private and not a slot. /** @short Slot to display the thumbnail image for the object */ void showThumbnail(); /** * @short Slot to update thumbnail image for the object, using the Thumbnail * Picker tool. * @sa ThumbnailPicker */ void updateThumbnail(); /** @short Slot for viewing the selected image or info URL in the web browser. */ void viewLink(); /** * Popup menu function: Add a custom Image or Information URL. * Opens the AddLinkDialog window. */ void addLink(); /** * @short Set the currently-selected URL resource. * * This function is needed because there are two QListWidgets, * each with its own selection. We need to know which the user selected most recently. */ void setCurrentLink(QListWidgetItem *it); /** * @short Rebuild the Image and Info URL lists for this object. * @note used when an item is added to either list. */ void updateLists(); /** * @short Open a dialog to edit a URL in either the Images or Info lists, * and update the user's *url.dat file. */ void editLinkDialog(); /** * @short remove a URL entry from either the Images or Info lists, and * update the user's *url.dat file. */ void removeLinkDialog(); /** * Open the web browser to the selected online astronomy database, * with a query to the object of this Detail Dialog. */ void viewADVData(); /** Save the User's text in the Log Tab to the userlog.dat file. */ void saveLogData(); /** Update View/Edit/Remove buttons */ void updateButtons(); private: /** Build the General Data Tab for the current object. */ void createGeneralTab(); /** Build the Position Tab for the current object. */ void createPositionTab(const KStarsDateTime &ut, GeoLocation *geo); /** * Build the Links Tab, populating the image and info lists with the * known URLs for the current Object. */ void createLinksTab(); /** Build the Advanced Tab */ void createAdvancedTab(); /** Build the Log Tab */ void createLogTab(); /** Populate the TreeView of known astronomical databases in the Advanced Tab */ void populateADVTree(); /** * Data for the Advanced Tab TreeView is stored in the file advinterface.dat. * This function parses advinterface.dat. */ QString parseADVData(const QString &link); /** * Update the local info_url and image_url files * @param type The URL type. 0 for Info Links, 1 for Images. * @param search_line The line to be search for in the local URL files * @param replace_line The replacement line once search_line is found. * @note If replace_line is empty, the function will remove search_line from the file */ void updateLocalDatabase(int type, const QString &search_line, const QString &replace_line = QString()); SkyObject *selectedObject { nullptr }; QPalette titlePalette; QListWidgetItem *m_CurrentLink { nullptr }; std::unique_ptr Thumbnail; DataWidget *Data { nullptr }; DataCometWidget *DataComet { nullptr }; PositionWidget *Pos { nullptr }; LinksWidget *Links { nullptr }; DatabaseWidget *Adv { nullptr }; LogWidget *Log { nullptr }; }; class DataWidget : public QFrame, public Ui::DetailsData { Q_OBJECT public: - explicit DataWidget(QWidget *parent = 0); + explicit DataWidget(QWidget *parent = nullptr); }; class DataCometWidget : public QFrame, public Ui::DetailsDataComet { Q_OBJECT public: - explicit DataCometWidget(QWidget *parent = 0); + explicit DataCometWidget(QWidget *parent = nullptr); }; class PositionWidget : public QFrame, public Ui::DetailsPosition { Q_OBJECT public: - explicit PositionWidget(QWidget *parent = 0); + explicit PositionWidget(QWidget *parent = nullptr); }; class LinksWidget : public QFrame, public Ui::DetailsLinks { Q_OBJECT public: - explicit LinksWidget(QWidget *parent = 0); + explicit LinksWidget(QWidget *parent = nullptr); }; class DatabaseWidget : public QFrame, public Ui::DetailsDatabase { Q_OBJECT public: - explicit DatabaseWidget(QWidget *parent = 0); + explicit DatabaseWidget(QWidget *parent = nullptr); }; class LogWidget : public QFrame, public Ui::DetailsLog { Q_OBJECT public: - explicit LogWidget(QWidget *parent = 0); + explicit LogWidget(QWidget *parent = nullptr); }; diff --git a/kstars/dialogs/exportimagedialog.cpp b/kstars/dialogs/exportimagedialog.cpp index 42457fa3d..72f3c5026 100644 --- a/kstars/dialogs/exportimagedialog.cpp +++ b/kstars/dialogs/exportimagedialog.cpp @@ -1,136 +1,136 @@ /*************************************************************************** exportimagedialog.cpp - K Desktop Planetarium ------------------- begin : Mon Jun 13 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #include #include #include #include #include "exportimagedialog.h" #include "kstars.h" #include "skymap.h" #include "printing/legend.h" #include "skyqpainter.h" #include "imageexporter.h" ExportImageDialogUI::ExportImageDialogUI(QWidget *parent) : QFrame(parent) { setupUi(this); } ExportImageDialog::ExportImageDialog(const QString &url, const QSize &size, ImageExporter *imgExporter) : QDialog((QWidget *)KStars::Instance()), m_KStars(KStars::Instance()), m_Url(url), m_Size(size) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif m_DialogUI = new ExportImageDialogUI(this); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(m_DialogUI); setLayout(mainLayout); 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())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(exportImage())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(close())); QPushButton *previewB = new QPushButton(i18n("Preview image")); buttonBox->addButton(previewB, QDialogButtonBox::ActionRole); connect(previewB, SIGNAL(clicked()), this, SLOT(previewImage())); connect(m_DialogUI->addLegendCheckBox, SIGNAL(toggled(bool)), this, SLOT(switchLegendEnabled(bool))); connect(m_DialogUI->addLegendCheckBox, SIGNAL(toggled(bool)), previewB, SLOT(setEnabled(bool))); m_ImageExporter = ((imgExporter) ? imgExporter : new ImageExporter(this)); setWindowTitle(i18n("Export sky image")); setupWidgets(); } void ExportImageDialog::switchLegendEnabled(bool enabled) { m_DialogUI->legendOrientationLabel->setEnabled(enabled); m_DialogUI->legendOrientationComboBox->setEnabled(enabled); m_DialogUI->legendTypeLabel->setEnabled(enabled); m_DialogUI->legendTypeComboBox->setEnabled(enabled); m_DialogUI->legendPositionLabel->setEnabled(enabled); m_DialogUI->legendPositionComboBox->setEnabled(enabled); } void ExportImageDialog::previewImage() { updateLegendSettings(); const Legend *legend = m_ImageExporter->getLegend(); // Preview current legend settings on sky map m_KStars->map()->setLegend(Legend(*legend)); m_KStars->map()->setPreviewLegend(true); // Update sky map m_KStars->map()->forceUpdate(true); // Hide export dialog hide(); } void ExportImageDialog::setupWidgets() { m_DialogUI->addLegendCheckBox->setChecked(true); m_DialogUI->legendOrientationComboBox->addItem(i18n("Horizontal")); m_DialogUI->legendOrientationComboBox->addItem(i18n("Vertical")); QStringList types; types << i18n("Full legend") << i18n("Scale with magnitudes chart") << i18n("Only scale") << i18n("Only magnitudes") << i18n("Only symbols"); m_DialogUI->legendTypeComboBox->addItems(types); QStringList positions; positions << i18n("Upper left corner") << i18n("Upper right corner") << i18n("Lower left corner") << i18n("Lower right corner"); m_DialogUI->legendPositionComboBox->addItems(positions); } void ExportImageDialog::updateLegendSettings() { Legend::LEGEND_ORIENTATION orientation = ((m_DialogUI->legendOrientationComboBox->currentIndex() == 1) ? Legend::LO_VERTICAL : Legend::LO_HORIZONTAL); Legend::LEGEND_TYPE type = static_cast(m_DialogUI->legendTypeComboBox->currentIndex()); Legend::LEGEND_POSITION pos = static_cast(m_DialogUI->legendPositionComboBox->currentIndex()); m_ImageExporter->setLegendProperties(type, orientation, pos); } void ExportImageDialog::exportImage() { qDebug() << "Exporting sky image"; updateLegendSettings(); m_ImageExporter->includeLegend(m_DialogUI->addLegendCheckBox->isChecked()); if (!m_ImageExporter->exportImage(m_Url)) { - KMessageBox::sorry(0, m_ImageExporter->getLastErrorMessage(), i18n("Could not export image")); + KMessageBox::sorry(nullptr, m_ImageExporter->getLastErrorMessage(), i18n("Could not export image")); } } diff --git a/kstars/dialogs/exportimagedialog.h b/kstars/dialogs/exportimagedialog.h index 1523a105c..9bf0df5de 100644 --- a/kstars/dialogs/exportimagedialog.h +++ b/kstars/dialogs/exportimagedialog.h @@ -1,79 +1,79 @@ /*************************************************************************** exportimagedialog.h - K Desktop Planetarium ------------------- begin : Mon Jun 13 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #ifndef EXPORTIMAGEDIALOG_H #define EXPORTIMAGEDIALOG_H #include "ui_exportimagedialog.h" #include "../printing/legend.h" #include class KStars; class QString; class QSize; class ImageExporter; // ExportImageDialog user interface. class ExportImageDialogUI : public QFrame, public Ui::ExportImageDialog { Q_OBJECT public: - explicit ExportImageDialogUI(QWidget *parent = 0); + explicit ExportImageDialogUI(QWidget *parent = nullptr); }; /** @short Export sky image dialog. This dialog enables user to set up basic legend properties before image is exported. */ class ExportImageDialog : public QDialog { Q_OBJECT public: /**short Default constructor. Creates dialog operating on passed URL and output image width and height. *@param url URL for exported image. *@param size size of exported image. *@param imgExporter A pointer to an ImageExporter that we can use instead of creating our own. if 0, we will create our own. */ - ExportImageDialog(const QString &url, const QSize &size, ImageExporter *imgExporter = 0); + ExportImageDialog(const QString &url, const QSize &size, ImageExporter *imgExporter = nullptr); /** @short Default destructor. */ ~ExportImageDialog() override {} inline void setOutputUrl(const QString &url) { m_Url = url; } inline void setOutputSize(const QSize &size) { m_Size = size; } private slots: void switchLegendEnabled(bool enabled); void previewImage(); void exportImage(); void setupWidgets(); void updateLegendSettings(); private: KStars *m_KStars; ExportImageDialogUI *m_DialogUI; QString m_Url; QSize m_Size; ImageExporter *m_ImageExporter; }; #endif // EXPORTIMAGEDIALOG_H diff --git a/kstars/dialogs/finddialog.cpp b/kstars/dialogs/finddialog.cpp index d2aa890bd..fd954e538 100644 --- a/kstars/dialogs/finddialog.cpp +++ b/kstars/dialogs/finddialog.cpp @@ -1,422 +1,422 @@ /*************************************************************************** finddialog.cpp - K Desktop Planetarium ------------------- begin : Wed Jul 4 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 "finddialog.h" #include "kstars.h" #include "kstarsdata.h" #include "Options.h" #include "detaildialog.h" #include "skyobjects/skyobject.h" #include "skyobjects/deepskyobject.h" #include "skycomponents/starcomponent.h" #include "skycomponents/syncedcatalogcomponent.h" #include "skycomponents/skymapcomposite.h" #include "tools/nameresolver.h" #include "skyobjectlistmodel.h" #include #include #include #include FindDialogUI::FindDialogUI(QWidget *parent) : QFrame(parent) { setupUi(this); FilterType->addItem(i18n("Any")); FilterType->addItem(i18n("Stars")); FilterType->addItem(i18n("Solar System")); FilterType->addItem(i18n("Open Clusters")); FilterType->addItem(i18n("Globular Clusters")); FilterType->addItem(i18n("Gaseous Nebulae")); FilterType->addItem(i18n("Planetary Nebulae")); FilterType->addItem(i18n("Galaxies")); FilterType->addItem(i18n("Comets")); FilterType->addItem(i18n("Asteroids")); FilterType->addItem(i18n("Constellations")); FilterType->addItem(i18n("Supernovae")); FilterType->addItem(i18n("Satellites")); SearchList->setMinimumWidth(256); SearchList->setMinimumHeight(320); } -FindDialog::FindDialog(QWidget *parent) : QDialog(parent), timer(0), m_targetObject(0) +FindDialog::FindDialog(QWidget *parent) : QDialog(parent), timer(nullptr), m_targetObject(nullptr) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif ui = new FindDialogUI(this); setWindowTitle(i18n("Find Object")); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(ui); setLayout(mainLayout); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(accepted()), this, SLOT(slotOk())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); okB = buttonBox->button(QDialogButtonBox::Ok); QPushButton *detailB = new QPushButton(i18n("Details...")); buttonBox->addButton(detailB, QDialogButtonBox::ActionRole); connect(detailB, SIGNAL(clicked()), this, SLOT(slotDetails())); ui->InternetSearchButton->setVisible(Options::resolveNamesOnline()); ui->InternetSearchButton->setEnabled(false); connect(ui->InternetSearchButton, SIGNAL(clicked()), this, SLOT(slotResolve())); ui->FilterType->setCurrentIndex(0); // show all types of objects fModel = new SkyObjectListModel(this); sortModel = new QSortFilterProxyModel(ui->SearchList); sortModel->setFilterCaseSensitivity(Qt::CaseInsensitive); sortModel->setSourceModel(fModel); sortModel->setSortRole(Qt::DisplayRole); sortModel->setFilterRole(Qt::DisplayRole); sortModel->setDynamicSortFilter(true); sortModel->sort(0); ui->SearchList->setModel(sortModel); // Connect signals to slots connect(ui->SearchBox, SIGNAL(textChanged(QString)), SLOT(enqueueSearch())); connect(ui->SearchBox, SIGNAL(returnPressed()), SLOT(slotOk())); connect(ui->FilterType, SIGNAL(activated(int)), this, SLOT(enqueueSearch())); connect(ui->SearchList, SIGNAL(doubleClicked(QModelIndex)), SLOT(slotOk())); // Set focus to object name edit ui->SearchBox->setFocus(); // First create and paint dialog and then load list QTimer::singleShot(0, this, SLOT(init())); listFiltered = false; } FindDialog::~FindDialog() { } void FindDialog::init() { ui->SearchBox->clear(); filterByType(); sortModel->sort(0); initSelection(); - m_targetObject = 0; + m_targetObject = nullptr; } void FindDialog::initSelection() { if (sortModel->rowCount() <= 0) { okB->setEnabled(false); return; } if (ui->SearchBox->text().isEmpty()) { //Pre-select the first item QModelIndex selectItem = sortModel->index(0, sortModel->filterKeyColumn(), QModelIndex()); switch (ui->FilterType->currentIndex()) { case 0: //All objects, choose Andromeda galaxy { QModelIndex qmi = fModel->index(fModel->indexOf(i18n("Andromeda Galaxy"))); selectItem = sortModel->mapFromSource(qmi); break; } case 1: //Stars, choose Aldebaran { QModelIndex qmi = fModel->index(fModel->indexOf(i18n("Aldebaran"))); selectItem = sortModel->mapFromSource(qmi); break; } case 2: //Solar system or Asteroids, choose Aaltje case 9: { QModelIndex qmi = fModel->index(fModel->indexOf(i18n("Aaltje"))); selectItem = sortModel->mapFromSource(qmi); break; } case 8: //Comets, choose 'Aarseth-Brewington (1989 W1)' { QModelIndex qmi = fModel->index(fModel->indexOf(i18n("Aarseth-Brewington (1989 W1)"))); selectItem = sortModel->mapFromSource(qmi); break; } } if (selectItem.isValid()) { ui->SearchList->selectionModel()->select(selectItem, QItemSelectionModel::ClearAndSelect); ui->SearchList->scrollTo(selectItem); ui->SearchList->setCurrentIndex(selectItem); okB->setEnabled(true); } } listFiltered = true; } void FindDialog::filterByType() { KStarsData *data = KStarsData::Instance(); switch (ui->FilterType->currentIndex()) { case 0: // All object types { QVector> allObjects; foreach (int type, data->skyComposite()->objectLists().keys()) { allObjects.append(data->skyComposite()->objectLists(SkyObject::TYPE(type))); } fModel->setSkyObjectsList(allObjects); break; } case 1: //Stars { QVector> starObjects; starObjects.append(data->skyComposite()->objectLists(SkyObject::STAR)); starObjects.append(data->skyComposite()->objectLists(SkyObject::CATALOG_STAR)); fModel->setSkyObjectsList(starObjects); break; } case 2: //Solar system { QVector> ssObjects; ssObjects.append(data->skyComposite()->objectLists(SkyObject::PLANET)); ssObjects.append(data->skyComposite()->objectLists(SkyObject::COMET)); ssObjects.append(data->skyComposite()->objectLists(SkyObject::ASTEROID)); ssObjects.append(data->skyComposite()->objectLists(SkyObject::MOON)); fModel->setSkyObjectsList(ssObjects); break; } case 3: //Open Clusters fModel->setSkyObjectsList(data->skyComposite()->objectLists(SkyObject::OPEN_CLUSTER)); break; case 4: //Globular Clusters fModel->setSkyObjectsList(data->skyComposite()->objectLists(SkyObject::GLOBULAR_CLUSTER)); break; case 5: //Gaseous nebulae fModel->setSkyObjectsList(data->skyComposite()->objectLists(SkyObject::GASEOUS_NEBULA)); break; case 6: //Planetary nebula fModel->setSkyObjectsList(data->skyComposite()->objectLists(SkyObject::PLANETARY_NEBULA)); break; case 7: //Galaxies fModel->setSkyObjectsList(data->skyComposite()->objectLists(SkyObject::GALAXY)); break; case 8: //Comets fModel->setSkyObjectsList(data->skyComposite()->objectLists(SkyObject::COMET)); break; case 9: //Asteroids fModel->setSkyObjectsList(data->skyComposite()->objectLists(SkyObject::ASTEROID)); break; case 10: //Constellations fModel->setSkyObjectsList(data->skyComposite()->objectLists(SkyObject::CONSTELLATION)); break; case 11: //Supernovae fModel->setSkyObjectsList(data->skyComposite()->objectLists(SkyObject::SUPERNOVA)); break; case 12: //Satellites fModel->setSkyObjectsList(data->skyComposite()->objectLists(SkyObject::SATELLITE)); break; } } void FindDialog::filterList() { QString SearchText = processSearchText(); sortModel->setFilterFixedString(SearchText); ui->InternetSearchButton->setText(i18n("or search the internet for %1", SearchText)); filterByType(); initSelection(); //Select the first item in the list that begins with the filter string if (!SearchText.isEmpty()) { QStringList mItems = fModel->filter(QRegExp('^' + SearchText, Qt::CaseInsensitive)); mItems.sort(); if (mItems.size()) { QModelIndex qmi = fModel->index(fModel->indexOf(mItems[0])); QModelIndex selectItem = sortModel->mapFromSource(qmi); if (selectItem.isValid()) { ui->SearchList->selectionModel()->select(selectItem, QItemSelectionModel::ClearAndSelect); ui->SearchList->scrollTo(selectItem); ui->SearchList->setCurrentIndex(selectItem); okB->setEnabled(true); } } ui->InternetSearchButton->setEnabled(!mItems.contains( SearchText)); // Disable searching the internet when an exact match for SearchText exists in KStars } else ui->InternetSearchButton->setEnabled(false); listFiltered = true; } SkyObject *FindDialog::selectedObject() const { QModelIndex i = ui->SearchList->currentIndex(); QVariant sObj = sortModel->data(sortModel->index(i.row(), 0), SkyObjectListModel::SkyObjectRole); return reinterpret_cast(sObj.value()); } void FindDialog::enqueueSearch() { listFiltered = false; if (timer) { timer->stop(); } else { timer = new QTimer(this); timer->setSingleShot(true); connect(timer, SIGNAL(timeout()), this, SLOT(filterList())); } timer->start(500); } // Process the search box text to replace equivalent names like "m93" with "m 93" QString FindDialog::processSearchText() { QRegExp re; QString searchtext = ui->SearchBox->text(); re.setCaseSensitivity(Qt::CaseInsensitive); // If it is an NGC/IC/M catalog number, as in "M 76" or "NGC 5139", check for absence of the space re.setPattern("^(m|ngc|ic)\\s*\\d*$"); if (ui->SearchBox->text().contains(re)) { re.setPattern("\\s*(\\d+)"); searchtext.replace(re, " \\1"); re.setPattern("\\s*$"); searchtext.remove(re); re.setPattern("^\\s*"); searchtext.remove(re); } // TODO after KDE 4.1 release: // If it is a IAU standard three letter abbreviation for a constellation, then go to that constellation // Check for genetive names of stars. Example: alp CMa must go to alpha Canis Majoris return searchtext; } void FindDialog::slotOk() { //If no valid object selected, show a sorry-box. Otherwise, emit accept() SkyObject *selObj; if (!listFiltered) { filterList(); } selObj = selectedObject(); finishProcessing(selObj, Options::resolveNamesOnline()); } void FindDialog::slotResolve() { - finishProcessing(0, true); + finishProcessing(nullptr, true); } void FindDialog::finishProcessing(SkyObject *selObj, bool resolve) { if (!selObj && resolve) { CatalogEntryData cedata; cedata = NameResolver::resolveName(processSearchText()); - DeepSkyObject *dso = 0; + DeepSkyObject *dso = nullptr; if (!std::isnan(cedata.ra) && !std::isnan(cedata.dec)) { dso = KStarsData::Instance()->skyComposite()->internetResolvedComponent()->addObject(cedata); if (dso) qDebug() << dso->ra0().toHMSString() << ";" << dso->dec0().toDMSString(); selObj = dso; } } m_targetObject = selObj; - if (selObj == 0) + if (selObj == nullptr) { QString message = i18n("No object named %1 found.", ui->SearchBox->text()); - KMessageBox::sorry(0, message, i18n("Bad object name")); + KMessageBox::sorry(nullptr, message, i18n("Bad object name")); } else { selObj->updateCoordsNow(KStarsData::Instance()->updateNum()); accept(); } } void FindDialog::keyPressEvent(QKeyEvent *e) { switch (e->key()) { case Qt::Key_Escape: reject(); break; case Qt::Key_Up: { int currentRow = ui->SearchList->currentIndex().row(); if (currentRow > 0) { QModelIndex selectItem = sortModel->index(currentRow - 1, sortModel->filterKeyColumn(), QModelIndex()); ui->SearchList->selectionModel()->setCurrentIndex(selectItem, QItemSelectionModel::SelectCurrent); } break; } case Qt::Key_Down: { int currentRow = ui->SearchList->currentIndex().row(); if (currentRow < sortModel->rowCount() - 1) { QModelIndex selectItem = sortModel->index(currentRow + 1, sortModel->filterKeyColumn(), QModelIndex()); ui->SearchList->selectionModel()->setCurrentIndex(selectItem, QItemSelectionModel::SelectCurrent); } break; } } } void FindDialog::slotDetails() { if (selectedObject()) { QPointer dd = new DetailDialog(selectedObject(), KStarsData::Instance()->ut(), KStarsData::Instance()->geo(), KStars::Instance()); dd->exec(); delete dd; } } diff --git a/kstars/dialogs/finddialog.h b/kstars/dialogs/finddialog.h index c03035269..480d5117e 100644 --- a/kstars/dialogs/finddialog.h +++ b/kstars/dialogs/finddialog.h @@ -1,134 +1,134 @@ /*************************************************************************** finddialog.h - K Desktop Planetarium ------------------- begin : Wed Jul 4 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. * * * ***************************************************************************/ #ifndef FINDDIALOG_H_ #define FINDDIALOG_H_ #include #include #include "ui_finddialog.h" class QTimer; class QStringListModel; class QSortFilterProxyModel; class SkyObjectListModel; class SkyObject; class FindDialogUI : public QFrame, public Ui::FindDialog { Q_OBJECT public: - explicit FindDialogUI(QWidget *parent = 0); + explicit FindDialogUI(QWidget *parent = nullptr); }; /** @class FindDialog * Dialog window for finding SkyObjects by name. The dialog contains * a QListBox showing the list of named objects, a QLineEdit for filtering * the list by name, and a QCombobox for filtering the list by object type. * * @short Find Object Dialog * @author Jason Harris * @version 1.0 */ class FindDialog : public QDialog { Q_OBJECT public: /**Constructor. Creates all widgets and packs them in QLayouts. Connects * Signals and Slots. Runs initObjectList(). */ - explicit FindDialog(QWidget *parent = 0); + explicit FindDialog(QWidget *parent = nullptr); /** Destructor */ ~FindDialog() override; /** * @return the target object (need not be the same as currently selected object!) * * @note Avoid using selectedObject() */ inline SkyObject *targetObject() { return m_targetObject; } public slots: /**When Text is entered in the QLineEdit, filter the List of objects * so that only objects which start with the filter text are shown. */ void filterList(); //FIXME: Still valid for QDialog? i.e., does QDialog have a slotOk() ? /** *Overloading the Standard QDialogBase slotOk() to show a "sorry" *message box if no object is selected and internet resolution was *disabled/failed when the user presses Ok. The window is not *closed in this case. */ void slotOk(); /** * @short This slot resolves the object on the internet, ignoring the selection on the list */ void slotResolve(); private slots: /** Init object list after opening dialog. */ void init(); /** Set the selected item to the first item in the list */ void initSelection(); void enqueueSearch(); void slotDetails(); protected: /**Process Keystrokes. The Up and Down arrow keys are used to select the * Previous/Next item in the listbox of named objects. The Esc key closes * the window with no selection, using reject(). * @param e The QKeyEvent pointer */ void keyPressEvent(QKeyEvent *e) override; /** @return the currently-selected item from the listbox of named objects */ SkyObject *selectedObject() const; private: /** @short Do some post processing on the search text to interpret what the user meant * This could include replacing text like "m93" with "m 93" */ QString processSearchText(); /** * @short Finishes the processing towards closing the dialog initiated by slotOk() or slotResolve() */ - void finishProcessing(SkyObject *selObj = 0, bool resolve = true); + void finishProcessing(SkyObject *selObj = nullptr, bool resolve = true); /** @short pre-filter the list of objects according to the * selected object type. */ void filterByType(); FindDialogUI *ui; SkyObjectListModel *fModel; QSortFilterProxyModel *sortModel; QTimer *timer; bool listFiltered; QPushButton *okB; SkyObject *m_targetObject; }; #endif diff --git a/kstars/dialogs/focusdialog.cpp b/kstars/dialogs/focusdialog.cpp index 2e91acb6a..fdae7e584 100644 --- a/kstars/dialogs/focusdialog.cpp +++ b/kstars/dialogs/focusdialog.cpp @@ -1,163 +1,163 @@ /*************************************************************************** focusdialog.cpp - description ------------------- begin : Sat Mar 23 2002 copyright : (C) 2002 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 "focusdialog.h" #include "dms.h" #include "kstars.h" #include "kstarsdata.h" #include "skymap.h" #include "skyobjects/skypoint.h" #include #include #include FocusDialogUI::FocusDialogUI(QWidget *parent) : QFrame(parent) { setupUi(this); } FocusDialog::FocusDialog() : QDialog(KStars::Instance()) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif //initialize point to the current focus position Point = SkyMap::Instance()->focus(); fd = new FocusDialogUI(this); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(fd); setLayout(mainLayout); setWindowTitle(i18n("Set Coordinates Manually")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(accepted()), this, SLOT(validatePoint())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); okB = buttonBox->button(QDialogButtonBox::Ok); okB->setEnabled(false); fd->epochBox->setValidator(new QDoubleValidator(fd->epochBox)); fd->raBox->setMinimumWidth(fd->raBox->fontMetrics().boundingRect("00h 00m 00s").width()); fd->azBox->setMinimumWidth(fd->raBox->fontMetrics().boundingRect("00h 00m 00s").width()); fd->raBox->setDegType(false); //RA box should be HMS-style fd->raBox->setFocus(); //set input focus connect(fd->raBox, SIGNAL(textChanged(QString)), this, SLOT(checkLineEdits())); connect(fd->decBox, SIGNAL(textChanged(QString)), this, SLOT(checkLineEdits())); connect(fd->azBox, SIGNAL(textChanged(QString)), this, SLOT(checkLineEdits())); connect(fd->altBox, SIGNAL(textChanged(QString)), this, SLOT(checkLineEdits())); } void FocusDialog::checkLineEdits() { bool raOk(false), decOk(false), azOk(false), altOk(false); fd->raBox->createDms(false, &raOk); fd->decBox->createDms(true, &decOk); fd->azBox->createDms(true, &azOk); fd->altBox->createDms(true, &altOk); if ((raOk && decOk) || (azOk && altOk)) okB->setEnabled(true); else okB->setEnabled(false); } void FocusDialog::validatePoint() { bool raOk(false), decOk(false), azOk(false), altOk(false); //false means expressed in hours dms ra(fd->raBox->createDms(false, &raOk)); dms dec(fd->decBox->createDms(true, &decOk)); QString message; if (raOk && decOk) { //make sure values are in valid range if (ra.Hours() < 0.0 || ra.Hours() > 24.0) message = i18n("The Right Ascension value must be between 0.0 and 24.0."); if (dec.Degrees() < -90.0 || dec.Degrees() > 90.0) message += '\n' + i18n("The Declination value must be between -90.0 and 90.0."); if (!message.isEmpty()) { - KMessageBox::sorry(0, message, i18n("Invalid Coordinate Data")); + KMessageBox::sorry(nullptr, message, i18n("Invalid Coordinate Data")); return; } Point->set(ra, dec); bool ok; double epoch0 = KStarsDateTime::stringToEpoch(fd->epochBox->text(), ok); long double jd0 = KStarsDateTime::epochToJd(epoch0); Point->apparentCoord(jd0, KStarsData::Instance()->ut().djd()); Point->EquatorialToHorizontal(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat()); QDialog::accept(); } else { dms az(fd->azBox->createDms(true, &azOk)); dms alt(fd->altBox->createDms(true, &altOk)); if (azOk && altOk) { //make sure values are in valid range if (az.Degrees() < 0.0 || az.Degrees() > 360.0) message = i18n("The Azimuth value must be between 0.0 and 360.0."); if (alt.Degrees() < -90.0 || alt.Degrees() > 90.0) message += '\n' + i18n("The Altitude value must be between -90.0 and 90.0."); if (!message.isEmpty()) { - KMessageBox::sorry(0, message, i18n("Invalid Coordinate Data")); + KMessageBox::sorry(nullptr, message, i18n("Invalid Coordinate Data")); return; } Point->setAz(az); Point->setAlt(alt); Point->HorizontalToEquatorial(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat()); UsedAltAz = true; QDialog::accept(); } else { QDialog::reject(); } } } QSize FocusDialog::sizeHint() const { return QSize(240, 210); } void FocusDialog::activateAzAltPage() const { fd->fdTab->setCurrentWidget(fd->aaTab); fd->azBox->setFocus(); } diff --git a/kstars/dialogs/fovdialog.cpp b/kstars/dialogs/fovdialog.cpp index 438b4c9ce..771a4048c 100644 --- a/kstars/dialogs/fovdialog.cpp +++ b/kstars/dialogs/fovdialog.cpp @@ -1,400 +1,400 @@ /*************************************************************************** fovdialog.cpp - description ------------------- begin : Fri 05 Sept 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 "fovdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kstars.h" #include "kstarsdata.h" #include "widgets/fovwidget.h" // This is needed to make FOV work with QVariant Q_DECLARE_METATYPE(FOV *) int FOVDialog::fovID = -1; namespace { // Try to convert text in KLine edit to double -inline double textToDouble(const QLineEdit *edit, bool *ok = 0) +inline double textToDouble(const QLineEdit *edit, bool *ok = nullptr) { return edit->text().replace(QLocale().decimalPoint(), ".").toDouble(ok); } // Extract FOV from QListWidget. No checking is done FOV *getFOV(QListWidgetItem *item) { return item->data(Qt::UserRole).value(); } // Convert double to QString QString toString(double x, int precision = 2) { return QString::number(x, 'f', precision).replace('.', QLocale().decimalPoint()); } } FOVDialogUI::FOVDialogUI(QWidget *parent) : QFrame(parent) { setupUi(this); } NewFOVUI::NewFOVUI(QWidget *parent) : QFrame(parent) { setupUi(this); } //---------FOVDialog---------------// FOVDialog::FOVDialog(QWidget *p) : QDialog(p) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif // Register FOV* data type if (fovID == -1) fovID = qRegisterMetaType("FOV*"); fov = new FOVDialogUI(this); setWindowTitle(i18n("Set FOV Indicator")); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(fov); setLayout(mainLayout); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Close); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(close())); connect(fov->FOVListBox, SIGNAL(currentRowChanged(int)), SLOT(slotSelect(int))); connect(fov->NewButton, SIGNAL(clicked()), SLOT(slotNewFOV())); connect(fov->EditButton, SIGNAL(clicked()), SLOT(slotEditFOV())); connect(fov->RemoveButton, SIGNAL(clicked()), SLOT(slotRemoveFOV())); // Read list of FOVs and for each FOV create listbox entry, which stores it. foreach (FOV *f, FOVManager::getFOVs()) { addListWidget(f); } } FOVDialog::~FOVDialog() { // Delete FOVs //for(int i = 0; i < fov->FOVListBox->count(); i++) { //delete getFOV( fov->FOVListBox->item(i) ); //} } QListWidgetItem *FOVDialog::addListWidget(FOV *f) { QListWidgetItem *item = new QListWidgetItem(f->name(), fov->FOVListBox); item->setData(Qt::UserRole, QVariant::fromValue(f)); return item; } void FOVDialog::slotSelect(int irow) { bool enable = irow >= 0; fov->RemoveButton->setEnabled(enable); fov->EditButton->setEnabled(enable); if (enable) { //paint dialog with selected FOV symbol fov->ViewBox->setFOV(getFOV(fov->FOVListBox->currentItem())); fov->ViewBox->update(); } } void FOVDialog::slotNewFOV() { QPointer newfdlg = new NewFOV(this); if (newfdlg->exec() == QDialog::Accepted) { FOV *newfov = new FOV(newfdlg->getFOV()); FOVManager::addFOV(newfov); addListWidget(newfov); fov->FOVListBox->setCurrentRow(fov->FOVListBox->count() - 1); } delete newfdlg; } void FOVDialog::slotEditFOV() { //Preload current values QListWidgetItem *item = fov->FOVListBox->currentItem(); - if (item == 0) + if (item == nullptr) return; FOV *f = item->data(Qt::UserRole).value(); // Create dialog QPointer newfdlg = new NewFOV(this, f); if (newfdlg->exec() == QDialog::Accepted) { // Overwrite FOV *f = newfdlg->getFOV(); fov->ViewBox->update(); } delete newfdlg; } void FOVDialog::slotRemoveFOV() { int i = fov->FOVListBox->currentRow(); if (i >= 0) { QListWidgetItem *item = fov->FOVListBox->takeItem(i); FOVManager::removeFOV(getFOV(item)); delete item; } } //-------------NewFOV------------------// NewFOV::NewFOV(QWidget *parent, const FOV *fov) : QDialog(parent), f() { ui = new NewFOVUI(this); setWindowTitle(i18n("New FOV Indicator")); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(ui); setLayout(mainLayout); 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); // Initialize FOV if required - if (fov != 0) + if (fov != nullptr) { f = *fov; ui->FOVName->setText(f.name()); ui->FOVEditX->setText(toString(f.sizeX())); ui->FOVEditY->setText(toString(f.sizeY())); ui->FOVEditOffsetX->setText(toString(f.offsetX())); ui->FOVEditOffsetY->setText(toString(f.offsetY())); ui->FOVEditRotation->setText(toString(f.rotation())); ui->ColorButton->setColor(QColor(f.color())); ui->ShapeBox->setCurrentIndex(f.shape()); ui->ViewBox->setFOV(&f); ui->ViewBox->update(); } connect(ui->FOVName, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV())); connect(ui->FOVEditX, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV())); connect(ui->FOVEditY, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV())); connect(ui->FOVEditOffsetX, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV())); connect(ui->FOVEditOffsetY, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV())); connect(ui->FOVEditRotation, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV())); connect(ui->ColorButton, SIGNAL(changed(QColor)), SLOT(slotUpdateFOV())); connect(ui->ShapeBox, SIGNAL(activated(int)), SLOT(slotUpdateFOV())); connect(ui->ComputeEyeFOV, SIGNAL(clicked()), SLOT(slotComputeFOV())); connect(ui->ComputeCameraFOV, SIGNAL(clicked()), SLOT(slotComputeFOV())); connect(ui->ComputeHPBW, SIGNAL(clicked()), SLOT(slotComputeFOV())); connect(ui->ComputeBinocularFOV, SIGNAL(clicked()), SLOT(slotComputeFOV())); connect(ui->ComputeTLengthFromFNum1, SIGNAL(clicked()), SLOT(slotComputeTelescopeFL())); connect(ui->ComputeTLengthFromFNum2, SIGNAL(clicked()), SLOT(slotComputeTelescopeFL())); // Populate eyepiece AFOV options. The userData field contains the apparent FOV associated with that option ui->EyepieceAFOV->insertItem(0, i18nc("Specify the apparent field of view (AFOV) manually", "Specify AFOV"), -1); ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Ramsden (Typical)"), 30); ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Orthoscopic (Typical)"), 45); ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Ploessl (Typical)"), 50); ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Erfle (Typical)"), 60); ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Tele Vue Radian"), 60); ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Baader Hyperion"), 68); ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Tele Vue Panoptic"), 68); ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Tele Vue Delos"), 72); ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Meade UWA"), 82); ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Tele Vue Nagler"), 82); ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Tele Vue Ethos (Typical)"), 100); connect(ui->EyepieceAFOV, SIGNAL(currentIndexChanged(int)), SLOT(slotEyepieceAFOVChanged(int))); ui->LinearFOVDistance->insertItem(0, i18n("1000 yards")); ui->LinearFOVDistance->insertItem(1, i18n("1000 meters")); connect(ui->LinearFOVDistance, SIGNAL(currentIndexChanged(int)), SLOT(slotBinocularFOVDistanceChanged(int))); slotUpdateFOV(); } void NewFOV::slotBinocularFOVDistanceChanged(int index) { QString text = (index == 0 ? i18n("feet") : i18n("meters")); ui->LabelUnits->setText(text); } void NewFOV::slotUpdateFOV() { bool okX, okY; f.setName(ui->FOVName->text()); float sizeX = textToDouble(ui->FOVEditX, &okX); float sizeY = textToDouble(ui->FOVEditY, &okY); if (okX && okY) f.setSize(sizeX, sizeY); float xoffset = textToDouble(ui->FOVEditOffsetX, &okX); float yoffset = textToDouble(ui->FOVEditOffsetY, &okY); if (okX && okY) f.setOffset(xoffset, yoffset); float rot = textToDouble(ui->FOVEditRotation, &okX); if (okX) f.setRotation(rot); f.setShape(ui->ShapeBox->currentIndex()); f.setColor(ui->ColorButton->color().name()); okB->setEnabled(!f.name().isEmpty() && okX && okY); ui->ViewBox->setFOV(&f); ui->ViewBox->update(); } void NewFOV::slotComputeFOV() { if (sender() == ui->ComputeEyeFOV && ui->TLength1->value() > 0.0) { ui->FOVEditX->setText(toString(60.0 * ui->EyeFOV->value() * ui->EyeLength->value() / ui->TLength1->value())); ui->FOVEditY->setText(ui->FOVEditX->text()); } else if (sender() == ui->ComputeCameraFOV && ui->TLength2->value() > 0.0) { double sx = (double)ui->ChipWidth->value() * 3438.0 / ui->TLength2->value(); double sy = (double)ui->ChipHeight->value() * 3438.0 / ui->TLength2->value(); //const double aspectratio = 3.0/2.0; // Use the default aspect ratio for DSLRs / Film (i.e. 3:2) ui->FOVEditX->setText(toString(sx)); ui->FOVEditY->setText(toString(sy)); } else if (sender() == ui->ComputeHPBW && ui->RTDiameter->value() > 0.0 && ui->WaveLength->value() > 0.0) { ui->FOVEditX->setText(toString(34.34 * 1.2 * ui->WaveLength->value() / ui->RTDiameter->value())); // Beam width for an antenna is usually a circle on the sky. ui->ShapeBox->setCurrentIndex(4); ui->FOVEditY->setText(ui->FOVEditX->text()); slotUpdateFOV(); } else if (sender() == ui->ComputeBinocularFOV && ui->LinearFOV->value() > 0.0 && ui->LinearFOVDistance->currentIndex() >= 0) { double sx = atan((double)ui->LinearFOV->value() / ((ui->LinearFOVDistance->currentIndex() == 0) ? 3000.0 : 1000.0)) * 180.0 * 60.0 / dms::PI; ui->FOVEditX->setText(toString(sx)); ui->FOVEditY->setText(ui->FOVEditX->text()); } } void NewFOV::slotEyepieceAFOVChanged(int index) { if (index == 0) { ui->EyeFOV->setEnabled(true); } else { bool ok; ui->EyeFOV->setEnabled(false); ui->EyeFOV->setValue(ui->EyepieceAFOV->itemData(index).toFloat(&ok)); Q_ASSERT(ok); } } void NewFOV::slotComputeTelescopeFL() { QObject *whichTab = sender(); TelescopeFL *telescopeFLDialog = new TelescopeFL(this); if (telescopeFLDialog->exec() == QDialog::Accepted) { Q_ASSERT(whichTab == ui->ComputeTLengthFromFNum1 || whichTab == ui->ComputeTLengthFromFNum2); ((whichTab == ui->ComputeTLengthFromFNum1) ? ui->TLength1 : ui->TLength2) ->setValue(telescopeFLDialog->computeFL()); } delete telescopeFLDialog; } //-------------TelescopeFL------------------// -TelescopeFL::TelescopeFL(QWidget *parent) : QDialog(parent), aperture(0), fNumber(0), apertureUnit(0) +TelescopeFL::TelescopeFL(QWidget *parent) : QDialog(parent), aperture(nullptr), fNumber(nullptr), apertureUnit(nullptr) { setWindowTitle(i18n("Telescope Focal Length Calculator")); //QWidget *mainWidget = new QWidget( this ); QGridLayout *mainLayout = new QGridLayout(this); setLayout(mainLayout); aperture = new QDoubleSpinBox(); aperture->setRange(0.0, 100000.0); aperture->setDecimals(2); aperture->setSingleStep(0.1); fNumber = new QDoubleSpinBox(); fNumber->setRange(0.0, 99.9); fNumber->setDecimals(2); fNumber->setSingleStep(0.1); apertureUnit = new QComboBox(this); apertureUnit->insertItem(0, i18nc("millimeters", "mm")); apertureUnit->insertItem(1, i18n("inch")); mainLayout->addWidget(new QLabel(i18n("Aperture diameter: "), this), 0, 0); mainLayout->addWidget(aperture, 0, 1); mainLayout->addWidget(apertureUnit, 0, 2); mainLayout->addWidget(new QLabel(i18nc("F-Number or F-Ratio of optical system", "F-Number: "), this), 1, 0); mainLayout->addWidget(fNumber, 1, 1); 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())); show(); } double TelescopeFL::computeFL() const { const double inch_to_mm = 25.4; // 1 inch, by definition, is 25.4 mm return (aperture->value() * fNumber->value() * ((apertureUnit->currentIndex() == 1) ? inch_to_mm : 1.0)); // Focal Length = Aperture * F-Number, by definition of F-Number } unsigned int FOVDialog::currentItem() const { return fov->FOVListBox->currentRow(); } diff --git a/kstars/dialogs/fovdialog.h b/kstars/dialogs/fovdialog.h index 5e45e6986..d3d80273c 100644 --- a/kstars/dialogs/fovdialog.h +++ b/kstars/dialogs/fovdialog.h @@ -1,128 +1,128 @@ /*************************************************************************** fovdialog.h - description ------------------- begin : Fri 05 Sept 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. * * * ***************************************************************************/ #ifndef FOVDIALOG_H_ #define FOVDIALOG_H_ #include #include #include #include "fov.h" #include "ui_fovdialog.h" #include "ui_newfov.h" class FOVDialogUI : public QFrame, public Ui::FOVDialog { Q_OBJECT public: - explicit FOVDialogUI(QWidget *parent = 0); + explicit FOVDialogUI(QWidget *parent = nullptr); }; class NewFOVUI : public QFrame, public Ui::NewFOV { Q_OBJECT public: - explicit NewFOVUI(QWidget *parent = 0); + explicit NewFOVUI(QWidget *parent = nullptr); }; /** @class FOVDialog * FOVDialog is dialog to select a Field-of-View indicator (or create a new one) *@author Jason Harris *@version 1.0 */ class FOVDialog : public QDialog { Q_OBJECT public: - explicit FOVDialog(QWidget *parent = 0); + explicit FOVDialog(QWidget *parent = nullptr); ~FOVDialog() override; private slots: void slotNewFOV(); void slotEditFOV(); void slotRemoveFOV(); void slotSelect(int); private: /** Add new widget to list box */ QListWidgetItem *addListWidget(FOV *f); unsigned int currentItem() const; FOVDialogUI *fov; static int fovID; }; /** @class NewFOV Dialog for defining a new FOV symbol *@author Jason Harris *@version 1.0 */ class NewFOV : public QDialog { Q_OBJECT public: /** Create new dialog * @param parent parent widget * @fov widget to copy data from. If it's empty will create empty one. */ - explicit NewFOV(QWidget *parent = 0, const FOV *fov = 0); + explicit NewFOV(QWidget *parent = nullptr, const FOV *fov = nullptr); ~NewFOV() override {} /** Return reference to FOV. */ const FOV &getFOV() const { return f; } public slots: void slotBinocularFOVDistanceChanged(int index); void slotUpdateFOV(); void slotComputeFOV(); void slotEyepieceAFOVChanged(int index); void slotComputeTelescopeFL(); private: FOV f; NewFOVUI *ui; QPushButton *okB; }; /** *@class TelescopeFL Dialog for calculating telescope focal length from f-number and diameter *@author Akarsh Simha *@version 1.0 */ class TelescopeFL : public QDialog { Q_OBJECT public: /** * Create a telescope focal length dialog * @param parent parent widget */ - explicit TelescopeFL(QWidget *parent = 0); + explicit TelescopeFL(QWidget *parent = nullptr); ~TelescopeFL() override {} /** * Compute and return the focal length in mm * @return focal length in mm */ double computeFL() const; private: QDoubleSpinBox *aperture, *fNumber; QComboBox *apertureUnit; }; #endif diff --git a/kstars/dialogs/locationdialog.cpp b/kstars/dialogs/locationdialog.cpp index 472e34b46..1c59e9f29 100644 --- a/kstars/dialogs/locationdialog.cpp +++ b/kstars/dialogs/locationdialog.cpp @@ -1,750 +1,750 @@ /*************************************************************************** locationdialog.cpp - K Desktop Planetarium ------------------- begin : Sun Feb 11 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 "locationdialog.h" #include "kspaths.h" #include "kstarsdata.h" #include "kstars_debug.h" #include #ifdef HAVE_GEOCLUE2 #include #endif #include #include #include #include #include #include #include #include #include #include #include LocationDialogUI::LocationDialogUI(QWidget *parent) : QFrame(parent) { setupUi(this); } -LocationDialog::LocationDialog(QWidget *parent) : QDialog(parent), timer(0) +LocationDialog::LocationDialog(QWidget *parent) : QDialog(parent), timer(nullptr) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif KStarsData *data = KStarsData::Instance(); SelectedCity = nullptr; ld = new LocationDialogUI(this); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(ld); setLayout(mainLayout); ld->MapView->setLocationDialog(this); setWindowTitle(i18n("Set Geographic Location")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(accepted()), this, SLOT(slotOk())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); for (int i = 0; i < 25; ++i) ld->TZBox->addItem(QLocale().toString((double)(i - 12))); //Populate DSTRuleBox foreach (const QString &key, data->getRulebook().keys()) { if (!key.isEmpty()) ld->DSTRuleBox->addItem(key); } ld->AddCityButton->setIcon(QIcon::fromTheme("list-add", QIcon(":/icons/breeze/default/list-add.svg"))); ld->RemoveButton->setIcon(QIcon::fromTheme("list-remove", QIcon(":/icons/breeze/default/list-remove.svg"))); ld->UpdateButton->setIcon(QIcon::fromTheme("svn-update", QIcon(":/icons/breeze/default/svn-update.svg"))); connect(ld->CityFilter, SIGNAL(textChanged(QString)), this, SLOT(enqueueFilterCity())); connect(ld->ProvinceFilter, SIGNAL(textChanged(QString)), this, SLOT(enqueueFilterCity())); connect(ld->CountryFilter, SIGNAL(textChanged(QString)), this, SLOT(enqueueFilterCity())); connect(ld->NewCityName, SIGNAL(textChanged(QString)), this, SLOT(nameChanged())); connect(ld->NewProvinceName, SIGNAL(textChanged(QString)), this, SLOT(nameChanged())); connect(ld->NewCountryName, SIGNAL(textChanged(QString)), this, SLOT(nameChanged())); connect(ld->NewLong, SIGNAL(textChanged(QString)), this, SLOT(dataChanged())); connect(ld->NewLat, SIGNAL(textChanged(QString)), this, SLOT(dataChanged())); connect(ld->TZBox, SIGNAL(activated(int)), this, SLOT(dataChanged())); connect(ld->DSTRuleBox, SIGNAL(activated(int)), this, SLOT(dataChanged())); connect(ld->GeoBox, SIGNAL(itemSelectionChanged()), this, SLOT(changeCity())); connect(ld->AddCityButton, SIGNAL(clicked()), this, SLOT(addCity())); connect(ld->ClearFieldsButton, SIGNAL(clicked()), this, SLOT(clearFields())); connect(ld->RemoveButton, SIGNAL(clicked()), this, SLOT(removeCity())); connect(ld->UpdateButton, SIGNAL(clicked()), this, SLOT(updateCity())); // FIXME Disable this until Qt5 works with Geoclue2 #ifdef HAVE_GEOCLUE_2 source = QGeoPositionInfoSource::createDefaultSource(this); source->setPreferredPositioningMethods(QGeoPositionInfoSource::SatellitePositioningMethods); qDebug() << "Last known position" << source->lastKnownPosition().coordinate(); connect(source, SIGNAL(positionUpdated(QGeoPositionInfo)), this, SLOT(positionUpdated(QGeoPositionInfo))); connect(source, SIGNAL(error(QGeoPositionInfoSource::Error)), this, SLOT(positionUpdateError(QGeoPositionInfoSource::Error))); connect(source, SIGNAL(updateTimeout()), this, SLOT(positionUpdateTimeout())); connect(ld->GetLocationButton, SIGNAL(clicked()), this, SLOT(requestUpdate())); #endif ld->DSTLabel->setText("" + i18n("DST Rule:") + ""); connect(ld->DSTLabel, SIGNAL(linkActivated(QString)), this, SLOT(showTZRules())); dataModified = false; nameModified = false; ld->AddCityButton->setEnabled(false); ld->errorLabel->setText(QString()); // FIXME Disable this until Qt5 works with Geoclue2 #ifdef HAVE_GEOCLUE_2 nam = new QNetworkAccessManager(this); connect(nam, SIGNAL(finished(QNetworkReply *)), this, SLOT(processLocationNameData(QNetworkReply *))); #endif initCityList(); resize(640, 480); } void LocationDialog::initCityList() { KStarsData *data = KStarsData::Instance(); foreach (GeoLocation *loc, data->getGeoList()) { ld->GeoBox->addItem(loc->fullName()); filteredCityList.append(loc); //If TZ is not an even integer value, add it to listbox if (loc->TZ0() - int(loc->TZ0()) && ld->TZBox->findText(QLocale().toString(loc->TZ0())) != -1) { for (int i = 0; i < ld->TZBox->count(); ++i) { if (ld->TZBox->itemText(i).toDouble() > loc->TZ0()) { ld->TZBox->addItem(QLocale().toString(loc->TZ0()), i - 1); break; } } } } //Sort the list of Cities alphabetically...note that filteredCityList may now have a different ordering! ld->GeoBox->sortItems(); ld->CountLabel->setText( i18np("One city matches search criteria", "%1 cities match search criteria", ld->GeoBox->count())); // attempt to highlight the current kstars location in the GeoBox - ld->GeoBox->setCurrentItem(0); + ld->GeoBox->setCurrentItem(nullptr); for (int i = 0; i < ld->GeoBox->count(); i++) { if (ld->GeoBox->item(i)->text() == data->geo()->fullName()) { ld->GeoBox->setCurrentRow(i); break; } } } void LocationDialog::enqueueFilterCity() { if (timer) timer->stop(); else { timer = new QTimer(this); timer->setSingleShot(true); connect(timer, SIGNAL(timeout()), this, SLOT(filterCity())); } timer->start(500); } void LocationDialog::filterCity() { KStarsData *data = KStarsData::Instance(); ld->GeoBox->clear(); //Do NOT delete members of filteredCityList! while (!filteredCityList.isEmpty()) filteredCityList.takeFirst(); nameModified = false; dataModified = false; ld->AddCityButton->setEnabled(false); ld->UpdateButton->setEnabled(false); foreach (GeoLocation *loc, data->getGeoList()) { QString sc(loc->translatedName()); QString ss(loc->translatedCountry()); QString sp = ""; if (!loc->province().isEmpty()) sp = loc->translatedProvince(); if (sc.startsWith(ld->CityFilter->text(), Qt::CaseInsensitive) && sp.startsWith(ld->ProvinceFilter->text(), Qt::CaseInsensitive) && ss.startsWith(ld->CountryFilter->text(), Qt::CaseInsensitive)) { ld->GeoBox->addItem(loc->fullName()); filteredCityList.append(loc); } } ld->GeoBox->sortItems(); ld->CountLabel->setText( i18np("One city matches search criteria", "%1 cities match search criteria", ld->GeoBox->count())); if (ld->GeoBox->count() > 0) // set first item in list as selected ld->GeoBox->setCurrentItem(ld->GeoBox->item(0)); ld->MapView->repaint(); } void LocationDialog::changeCity() { KStarsData *data = KStarsData::Instance(); //when the selected city changes, set newCity, and redraw map - SelectedCity = 0L; + SelectedCity = nullptr; if (ld->GeoBox->currentItem()) { for (int i = 0; i < filteredCityList.size(); ++i) { GeoLocation *loc = filteredCityList.at(i); if (loc->fullName() == ld->GeoBox->currentItem()->text()) { SelectedCity = loc; break; } } } ld->MapView->repaint(); //Fill the fields at the bottom of the window with the selected city's data. if (SelectedCity) { ld->NewCityName->setText(SelectedCity->translatedName()); if (SelectedCity->province().isEmpty()) ld->NewProvinceName->setText(QString()); else ld->NewProvinceName->setText(SelectedCity->translatedProvince()); ld->NewCountryName->setText(SelectedCity->translatedCountry()); ld->NewLong->showInDegrees(SelectedCity->lng()); ld->NewLat->showInDegrees(SelectedCity->lat()); ld->TZBox->setEditText(QLocale().toString(SelectedCity->TZ0())); //Pick the City's rule from the rulebook for (int i = 0; i < ld->DSTRuleBox->count(); ++i) { TimeZoneRule tzr = data->getRulebook().value(ld->DSTRuleBox->itemText(i)); if (tzr.equals(SelectedCity->tzrule())) { ld->DSTRuleBox->setCurrentIndex(i); break; } } ld->RemoveButton->setEnabled(SelectedCity->isReadOnly() == false); } nameModified = false; dataModified = false; ld->AddCityButton->setEnabled(false); ld->UpdateButton->setEnabled(false); } bool LocationDialog::addCity() { return updateCity(CITY_ADD); } bool LocationDialog::updateCity() { if (SelectedCity == nullptr) return false; return updateCity(CITY_UPDATE); } bool LocationDialog::removeCity() { if (SelectedCity == nullptr) return false; return updateCity(CITY_REMOVE); } bool LocationDialog::updateCity(CityOperation operation) { if (operation == CITY_REMOVE) { QString message = i18n("Are you sure you want to remove %1?", selectedCityName()); - if (KMessageBox::questionYesNo(0, message, i18n("Remove City?")) == KMessageBox::No) + if (KMessageBox::questionYesNo(nullptr, message, i18n("Remove City?")) == KMessageBox::No) return false; //user answered No. } else if (!nameModified && !dataModified) { QString message = i18n("This city already exists in the database."); - KMessageBox::sorry(0, message, i18n("Error: Duplicate Entry")); + KMessageBox::sorry(nullptr, message, i18n("Error: Duplicate Entry")); return false; } bool latOk(false), lngOk(false), tzOk(false); dms lat = ld->NewLat->createDms(true, &latOk); dms lng = ld->NewLong->createDms(true, &lngOk); QString TimeZoneString = ld->TZBox->lineEdit()->text(); TimeZoneString.replace(QLocale().decimalPoint(), "."); double TZ = TimeZoneString.toDouble(&tzOk); if (ld->NewCityName->text().isEmpty() || ld->NewCountryName->text().isEmpty()) { QString message = i18n("All fields (except province) must be filled to add this location."); - KMessageBox::sorry(0, message, i18n("Fields are Empty")); + KMessageBox::sorry(nullptr, message, i18n("Fields are Empty")); return false; } else if (!latOk || !lngOk) { QString message = i18n("Could not parse the Latitude/Longitude."); - KMessageBox::sorry(0, message, i18n("Bad Coordinates")); + KMessageBox::sorry(nullptr, message, i18n("Bad Coordinates")); return false; } else if (!tzOk) { QString message = i18n("Could not parse coordinates."); - KMessageBox::sorry(0, message, i18n("Bad Coordinates")); + KMessageBox::sorry(nullptr, message, i18n("Bad Coordinates")); return false; } // If name is still the same then it's an update operation if (operation == CITY_ADD && !nameModified) operation = CITY_UPDATE; /*if ( !nameModified ) { QString message = i18n( "Really override original data for this city?" ); if ( KMessageBox::questionYesNo( 0, message, i18n( "Override Existing Data?" ), KGuiItem(i18n("Override Data")), KGuiItem(i18n("Do Not Override"))) == KMessageBox::No ) return false; //user answered No. }*/ QSqlDatabase mycitydb = QSqlDatabase::database("mycitydb"); QString dbfile = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "mycitydb.sqlite"; // If it doesn't exist, create it if (QFile::exists(dbfile) == false) { mycitydb.setDatabaseName(dbfile); mycitydb.open(); QSqlQuery create_query(mycitydb); QString query("CREATE TABLE city ( " "id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, " "Name TEXT DEFAULT NULL, " "Province TEXT DEFAULT NULL, " "Country TEXT DEFAULT NULL, " "Latitude TEXT DEFAULT NULL, " "Longitude TEXT DEFAULT NULL, " "TZ REAL DEFAULT NULL, " "TZRule TEXT DEFAULT NULL)"); if (create_query.exec(query) == false) { qCWarning(KSTARS) << create_query.lastError(); return false; } } else if (mycitydb.open() == false) { qCWarning(KSTARS) << mycitydb.lastError(); return false; } //Strip off white space QString name = ld->NewCityName->text().trimmed(); QString province = ld->NewProvinceName->text().trimmed(); QString country = ld->NewCountryName->text().trimmed(); QString TZrule = ld->DSTRuleBox->currentText(); GeoLocation *g = nullptr; switch (operation) { case CITY_ADD: { QSqlQuery add_query(mycitydb); add_query.prepare("INSERT INTO city(Name, Province, Country, Latitude, Longitude, TZ, TZRule) " "VALUES(:Name, :Province, :Country, :Latitude, :Longitude, :TZ, :TZRule)"); add_query.bindValue(":Name", name); add_query.bindValue(":Province", province); add_query.bindValue(":Country", country); add_query.bindValue(":Latitude", lat.toDMSString()); add_query.bindValue(":Longitude", lng.toDMSString()); add_query.bindValue(":TZ", TZ); add_query.bindValue(":TZRule", TZrule); if (add_query.exec() == false) { qCWarning(KSTARS) << add_query.lastError(); return false; } //Add city to geoList...don't need to insert it alphabetically, since we always sort GeoList g = new GeoLocation(lng, lat, name, province, country, TZ, &KStarsData::Instance()->Rulebook[TZrule]); KStarsData::Instance()->getGeoList().append(g); } break; case CITY_UPDATE: { g = SelectedCity; QSqlQuery update_query(mycitydb); update_query.prepare("UPDATE city SET Name = :newName, Province = :newProvince, Country = :newCountry, " "Latitude = :Latitude, Longitude = :Longitude, TZ = :TZ, TZRule = :TZRule WHERE " "Name = :Name AND Province = :Province AND Country = :Country"); update_query.bindValue(":newName", name); update_query.bindValue(":newProvince", province); update_query.bindValue(":newCountry", country); update_query.bindValue(":Name", SelectedCity->name()); update_query.bindValue(":Province", SelectedCity->province()); update_query.bindValue(":Country", SelectedCity->country()); update_query.bindValue(":Latitude", lat.toDMSString()); update_query.bindValue(":Longitude", lng.toDMSString()); update_query.bindValue(":TZ", TZ); update_query.bindValue(":TZRule", TZrule); if (update_query.exec() == false) { qCWarning(KSTARS) << update_query.lastError() << endl; return false; } g->setName(name); g->setProvince(province); g->setCountry(country); g->setLat(lat); g->setLong(lng); g->setTZ(TZ); g->setTZRule(&KStarsData::Instance()->Rulebook[TZrule]); } break; case CITY_REMOVE: { g = SelectedCity; QSqlQuery delete_query(mycitydb); delete_query.prepare("DELETE FROM city WHERE Name = :Name AND Province = :Province AND Country = :Country"); delete_query.bindValue(":Name", name); delete_query.bindValue(":Province", province); delete_query.bindValue(":Country", country); if (delete_query.exec() == false) { qCWarning(KSTARS) << delete_query.lastError() << endl; return false; } filteredCityList.removeOne(g); KStarsData::Instance()->getGeoList().removeOne(g); delete g; g = nullptr; } break; } //(possibly) insert new city into GeoBox by running filterCity() filterCity(); //Attempt to highlight new city in list - ld->GeoBox->setCurrentItem(0); + ld->GeoBox->setCurrentItem(nullptr); if (g && ld->GeoBox->count()) { for (int i = 0; i < ld->GeoBox->count(); i++) { if (ld->GeoBox->item(i)->text() == g->fullName()) { ld->GeoBox->setCurrentRow(i); break; } } } mycitydb.commit(); mycitydb.close(); return true; } void LocationDialog::findCitiesNear(int lng, int lat) { KStarsData *data = KStarsData::Instance(); //find all cities within 3 degrees of (lng, lat); list them in GeoBox ld->GeoBox->clear(); //Remember, do NOT delete members of filteredCityList while (!filteredCityList.isEmpty()) filteredCityList.takeFirst(); foreach (GeoLocation *loc, data->getGeoList()) { if ((abs(lng - int(loc->lng()->Degrees())) < 3) && (abs(lat - int(loc->lat()->Degrees())) < 3)) { ld->GeoBox->addItem(loc->fullName()); filteredCityList.append(loc); } } ld->GeoBox->sortItems(); ld->CountLabel->setText( i18np("One city matches search criteria", "%1 cities match search criteria", ld->GeoBox->count())); if (ld->GeoBox->count() > 0) // set first item in list as selected ld->GeoBox->setCurrentItem(ld->GeoBox->item(0)); repaint(); } bool LocationDialog::checkLongLat() { if (ld->NewLong->text().isEmpty() || ld->NewLat->text().isEmpty()) return false; bool ok; double lng = ld->NewLong->createDms(true, &ok).Degrees(); if (!ok) return false; double lat = ld->NewLat->createDms(true, &ok).Degrees(); if (!ok) return false; if (fabs(lng) > 180 || fabs(lat) > 90) return false; return true; } void LocationDialog::clearFields() { ld->CityFilter->clear(); ld->ProvinceFilter->clear(); ld->CountryFilter->clear(); ld->NewCityName->clear(); ld->NewProvinceName->clear(); ld->NewCountryName->clear(); ld->NewLong->clearFields(); ld->NewLat->clearFields(); ld->TZBox->lineEdit()->setText(QLocale().toString(0.0)); ld->DSTRuleBox->setCurrentIndex(0); nameModified = true; dataModified = false; ld->AddCityButton->setEnabled(false); ld->UpdateButton->setEnabled(false); ld->NewCityName->setFocus(); } void LocationDialog::showTZRules() { QStringList lines; lines.append(i18n(" Start Date (Start Time) / Revert Date (Revert Time)")); lines.append(" "); lines.append(i18n("--: No DST correction")); lines.append(i18n("AU: last Sun in Oct. (02:00) / last Sun in Mar. (02:00)")); lines.append(i18n("BZ: 2nd Sun in Oct. (00:00) / 3rd Sun in Feb. (00:00)")); lines.append(i18n("CH: 2nd Sun in Apr. (00:00) / 2nd Sun in Sep. (00:00)")); lines.append(i18n("CL: 2nd Sun in Oct. (04:00) / 2nd Sun in Mar. (04:00)")); lines.append(i18n("CZ: 1st Sun in Oct. (02:45) / 3rd Sun in Mar. (02:45)")); lines.append(i18n("EE: Last Sun in Mar. (00:00) / Last Sun in Oct. (02:00)")); lines.append(i18n("EG: Last Fri in Apr. (00:00) / Last Thu in Sep. (00:00)")); lines.append(i18n("EU: Last Sun in Mar. (01:00) / Last Sun in Oct. (01:00)")); lines.append(i18n("FK: 1st Sun in Sep. (02:00) / 3rd Sun in Apr. (02:00)")); lines.append(i18n("HK: 2nd Sun in May (03:30) / 3rd Sun in Oct. (03:30)")); lines.append(i18n("IQ: Apr 1 (03:00) / Oct. 1 (00:00)")); lines.append(i18n("IR: Mar 21 (00:00) / Sep. 22 (00:00)")); lines.append(i18n("JD: Last Thu in Mar. (00:00) / Last Thu in Sep. (00:00)")); lines.append(i18n("LB: Last Sun in Mar. (00:00) / Last Sun in Oct. (00:00)")); lines.append(i18n("MX: 1st Sun in May (02:00) / Last Sun in Sep. (02:00)")); lines.append(i18n("NB: 1st Sun in Sep. (02:00) / 1st Sun in Apr. (02:00)")); lines.append(i18n("NZ: 1st Sun in Oct. (02:00) / 3rd Sun in Mar. (02:00)")); lines.append(i18n("PY: 1st Sun in Oct. (00:00) / 1st Sun in Mar. (00:00)")); lines.append(i18n("RU: Last Sun in Mar. (02:00) / Last Sun in Oct. (02:00)")); lines.append(i18n("SK: 2nd Sun in May (00:00) / 2nd Sun in Oct. (00:00)")); lines.append(i18n("SY: Apr. 1 (00:00) / Oct. 1 (00:00)")); lines.append(i18n("TG: 1st Sun in Nov. (02:00) / Last Sun in Jan. (02:00)")); lines.append(i18n("TS: 1st Sun in Oct. (02:00) / Last Sun in Mar. (02:00)")); lines.append(i18n("US: 1st Sun in Apr. (02:00) / Last Sun in Oct. (02:00)")); lines.append(i18n("ZN: Apr. 1 (01:00) / Oct. 1 (00:00)")); QString message = i18n("Daylight Saving Time Rules"); QPointer tzd = new QDialog(this); tzd->setWindowTitle(message); QListWidget *lw = new QListWidget(tzd); lw->addItems(lines); //This is pretty lame...I have to measure the width of the first item in the //list widget, in order to set its width properly. Why doesn't it just resize //the widget to fit the contents automatically? I tried setting the sizePolicy, //no joy... int w = int(1.1 * lw->visualItemRect(lw->item(0)).width()); lw->setMinimumWidth(w); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(lw); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), tzd, SLOT(reject())); tzd->setLayout(mainLayout); tzd->exec(); delete tzd; } void LocationDialog::nameChanged() { nameModified = true; dataChanged(); } //do not enable Add button until all data are present and valid. void LocationDialog::dataChanged() { dataModified = true; ld->AddCityButton->setEnabled(nameModified && !ld->NewCityName->text().isEmpty() && !ld->NewCountryName->text().isEmpty() && checkLongLat()); if (SelectedCity) ld->UpdateButton->setEnabled(SelectedCity->isReadOnly() == false && !ld->NewCityName->text().isEmpty() && !ld->NewCountryName->text().isEmpty() && checkLongLat()); if (!addCityEnabled()) { if (ld->NewCityName->text().isEmpty()) { ld->errorLabel->setText(i18n("Cannot add new location -- city name blank")); } else if (ld->NewCountryName->text().isEmpty()) { ld->errorLabel->setText(i18n("Cannot add new location -- country name blank")); } else if (!checkLongLat()) { ld->errorLabel->setText(i18n("Cannot add new location -- invalid latitude / longitude")); } else { ld->errorLabel->setText(i18n("Cannot add new location -- please check all fields")); } } else { ld->errorLabel->setText(QString()); } } void LocationDialog::slotOk() { if (addCityEnabled()) { if (addCity()) accept(); } else accept(); } bool LocationDialog::addCityEnabled() { return ld->AddCityButton->isEnabled(); } // FIXME Disable this until Qt5 works with Geoclue2 #ifdef HAVE_GEOCLUE_2 void LocationDialog::getNameFromCoordinates(double latitude, double longitude) { QString lat = QString::number(latitude); QString lon = QString::number(longitude); QString latlng(lat + ", " + lon); QUrl url("http://maps.googleapis.com/maps/api/geocode/json"); QUrlQuery query; query.addQueryItem("latlng", latlng); url.setQuery(query); qDebug() << "submitting request"; nam->get(QNetworkRequest(url)); connect(nam, SIGNAL(finished(QNetworkReply *)), this, SLOT(processLocationNameData(QNetworkReply *))); } void LocationDialog::processLocationNameData(QNetworkReply *networkReply) { if (!networkReply) return; if (!networkReply->error()) { QJsonDocument document = QJsonDocument::fromJson(networkReply->readAll()); if (document.isObject()) { QJsonObject obj = document.object(); QJsonValue val; if (obj.contains(QStringLiteral("results"))) { val = obj["results"]; QString city = val.toArray()[0].toObject()["address_components"].toArray()[2].toObject()["long_name"].toString(); QString region = val.toArray()[0].toObject()["address_components"].toArray()[3].toObject()["long_name"].toString(); QString country = val.toArray()[0].toObject()["address_components"].toArray()[4].toObject()["long_name"].toString(); //emit newNameFromCoordinates(city, region, country); } else { } } } networkReply->deleteLater(); } void LocationDialog::requestUpdate() { source->requestUpdate(15000); } void LocationDialog::positionUpdated(const QGeoPositionInfo &info) { qDebug() << "Position updated:" << info; } void LocationDialog::positionUpdateError(QGeoPositionInfoSource::Error error) { qDebug() << "Positon update error: " << error; } void LocationDialog::positionUpdateTimeout() { qDebug() << "Timed out!"; qDebug() << source->error(); } #endif diff --git a/kstars/dialogs/locationdialog.h b/kstars/dialogs/locationdialog.h index af0cc9ed3..633d66221 100644 --- a/kstars/dialogs/locationdialog.h +++ b/kstars/dialogs/locationdialog.h @@ -1,207 +1,207 @@ /*************************************************************************** locationdialog.h - K Desktop Planetarium ------------------- begin : Sun Feb 11 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 "geolocation.h" #include "ui_locationdialog.h" #include #ifdef HAVE_GEOCLUE2 #include #include #endif #include #include class QTimer; class QNetworkAccessManager; class QNetworkReply; class LocationDialogUI : public QFrame, public Ui::LocationDialog { Q_OBJECT public: - explicit LocationDialogUI(QWidget *parent = 0); + explicit LocationDialogUI(QWidget *parent = nullptr); }; /** * @class LocationDialog * Dialog for changing the geographic location of the observer. The * dialog is divided into two sections. * * The top section allows the location to be selected from a database * of 2000 cities. It contains a MapCanvas (showing map of the globe * with cities overlaid, with a handler for mouse clicks), a QListBox * containing the names of cities in the database, and three QLineEdit * widgets, which allow the user to filter the List by the name of the * City, Province, and Country. In addition, the List * can be filtered by location, by clicking anywhere in the MapCanvas. * Doing so will display cities within 2 degrees of the clicked position. * * The bottom section allows the location to be specified manually. * The Longitude, Latitude, City name, Province/State name, and Country name * are entered into KLineEdits. There is also a QPushButton for adding the * location to the custom Cities database. If the user selects "Add" without * filling in all of the manual entry fields, an error message is displayed. * * The user can fetch current geographic location from QtPosition system. The actual * underlying location source depends on the OS and what modules are currently available, if any. * * @short Geographic Location dialog * @author Jason Harris * @author Jasem Mutlaq * @author Artem Fedoskin * @version 1.1 */ class LocationDialog : public QDialog { Q_OBJECT public: typedef enum { CITY_ADD, CITY_UPDATE, CITY_REMOVE } CityOperation; /** * Constructor. Create all widgets, and pack them into QLayouts. * Connect Signals to Slots. Run initCityList(). */ explicit LocationDialog(QWidget *parent); /** * Initialize list of cities. Note that the database is not read in here, * that is done in the KStars constructor. This simply loads the local QListBox * with the names of the cities from the kstarsData object. */ void initCityList(void); /** @return pointer to the highlighted city in the List. */ GeoLocation *selectedCity() const { return SelectedCity; } /** @return pointer to the List of filtered city pointers. */ QList filteredList() { return filteredCityList; } /** * @short Show only cities within 3 degrees of point specified by arguments * @param longitude the longitude of the search point (int) * @param latitude the latitude of the search point (int) */ void findCitiesNear(int longitude, int latitude); /** @return the city name of the selected location. */ QString selectedCityName() const { return SelectedCity->translatedName(); } /** @return the province name of the selected location. */ QString selectedProvinceName() const { return SelectedCity->translatedProvince(); } /** @return the country name of the selected location. */ QString selectedCountryName() const { return SelectedCity->translatedCountry(); } /** @return true if the AddCityBUtton is enabled */ bool addCityEnabled(); public slots: /** * When text is entered in the City/Province/Country Filter KLineEdits, the List of cities is * trimmed to show only cities beginning with the entered text. Also, the QMemArray of ID * numbers is kept in sync with the filtered list. */ void filterCity(); /** * @short Filter by city / province / country only after a few milliseconds */ void enqueueFilterCity(); /** * When the selected city in the QListBox changes, repaint the MapCanvas * so that the crosshairs icon appears on the newly selected city. */ void changeCity(); /** * When the "Add new city" QPushButton is clicked, add the manually-entered * city information to the user's custom city database. * @return true on success */ bool addCity(); /** * When the "Update City" QPushButton is clicked, update the city * information in the user's custom city database. * @return true on success */ bool updateCity(); /** * When the "Remove City" QPushButton is clicked, remove the * city information from the user's custom city database. * @return true on success */ bool removeCity(); /** * @brief updateCity Adds, updates, or removes a city from the user's database. * @param operation Add, update, or remove city * @return true on success */ bool updateCity(CityOperation operation); /** * @brief getNameFromCoordinates Given the current latitude and longitude, use Google Location API services to reverse lookup * the city, province, and country located at the requested position. * @param latitude Latitude in degrees * @param longitude Longitude is degrees */ // FIXME Disable this until Qt5 works with Geoclue2 #ifdef HAVE_GEOCLUE_2 void getNameFromCoordinates(double latitude, double longitude); #endif void clearFields(); void showTZRules(); void nameChanged(); void dataChanged(); void slotOk(); protected slots: // FIXME Disable this until Qt5 works with Geoclue2 #ifdef HAVE_GEOCLUE_2 void processLocationNameData(QNetworkReply *rep); void requestUpdate(); void positionUpdated(const QGeoPositionInfo &info); void positionUpdateError(QGeoPositionInfoSource::Error error); void positionUpdateTimeout(); #endif private: /** Make sure Longitude and Latitude values are valid. */ bool checkLongLat(); bool dataModified { false }; bool nameModified { false }; LocationDialogUI *ld { nullptr }; GeoLocation *SelectedCity { nullptr }; QList filteredCityList; QTimer *timer { nullptr }; //Retrieve the name of city #ifdef HAVE_GEOCLUE_2 QNetworkAccessManager *nam { nullptr }; QPointer source; #endif }; diff --git a/kstars/dialogs/timedialog.cpp b/kstars/dialogs/timedialog.cpp index 82c26ac2c..ba9eefb49 100644 --- a/kstars/dialogs/timedialog.cpp +++ b/kstars/dialogs/timedialog.cpp @@ -1,126 +1,126 @@ /*************************************************************************** timedialog.cpp - K Desktop Planetarium ------------------- begin : Sun Feb 11 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 "timedialog.h" #include #include #include #include #include #include #include #include #include "kstarsdatetime.h" #include "kstarsdata.h" #include "simclock.h" #include "geolocation.h" TimeDialog::TimeDialog(const KStarsDateTime &now, GeoLocation *_geo, QWidget *parent, bool UTCFrame) : QDialog(parent), geo(_geo) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif UTCNow = UTCFrame; QFrame *page = new QFrame(this); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(page); setLayout(mainLayout); if (UTCNow) setWindowTitle(i18nc("set clock to a new time", "Set UTC Time")); else setWindowTitle(i18nc("set clock to a new time", "Set Time")); 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())); vlay = new QVBoxLayout(page); vlay->setMargin(2); vlay->setSpacing(2); hlay = new QHBoxLayout(); //this layout will be added to the VLayout hlay->setSpacing(2); dPicker = new KDatePicker(now.date(), page); tEdit = new QTimeEdit(now.time(), page); NowButton = new QPushButton(page); NowButton->setObjectName("NowButton"); NowButton->setText(UTCNow ? i18n("UTC Now") : i18n("Now")); - vlay->addWidget(dPicker, 0, 0); + vlay->addWidget(dPicker, 0, nullptr); vlay->addLayout(hlay, 0); hlay->addWidget(tEdit); hlay->addWidget(NowButton); vlay->activate(); QObject::connect(NowButton, SIGNAL(clicked()), this, SLOT(setNow())); } //Add handler for Escape key to close window //Use keyReleaseEvent because keyPressEvents are already consumed //by the KDatePicker. void TimeDialog::keyReleaseEvent(QKeyEvent *kev) { switch (kev->key()) { case Qt::Key_Escape: { close(); break; } default: { kev->ignore(); break; } } } void TimeDialog::setNow(void) { KStarsDateTime dt(KStarsDateTime::currentDateTimeUtc()); if (!UTCNow) dt = geo->UTtoLT(dt); dPicker->setDate(dt.date()); tEdit->setTime(dt.time()); } QTime TimeDialog::selectedTime(void) { return tEdit->time(); } QDate TimeDialog::selectedDate(void) { return dPicker->date(); } KStarsDateTime TimeDialog::selectedDateTime(void) { return KStarsDateTime(selectedDate(), selectedTime()); } diff --git a/kstars/htmesh/HTMesh.cpp b/kstars/htmesh/HTMesh.cpp index fd2a6d48f..40109688d 100644 --- a/kstars/htmesh/HTMesh.cpp +++ b/kstars/htmesh/HTMesh.cpp @@ -1,279 +1,279 @@ #include #include #include "HTMesh.h" #include "MeshBuffer.h" #include "MeshIterator.h" #include "SpatialVector.h" #include "SpatialIndex.h" #include "RangeConvex.h" #include "HtmRange.h" #include "HtmRangeIterator.h" /****************************************************************************** * Note: There is "complete" checking for duplicate points in the line and * polygon intersection routines below. This may have a slight performance * impact on indexing lines and polygons. * * -- James B. Bowlin *****************************************************************************/ HTMesh::HTMesh(int level, int buildLevel, int numBuffers) : m_level(level), m_buildLevel(buildLevel), m_numBuffers(numBuffers), htmDebug(0) { name = "HTMesh"; if (m_buildLevel > 0) { if (m_buildLevel > m_level) m_buildLevel = m_level; htm = new SpatialIndex(m_level, m_buildLevel); } else { htm = new SpatialIndex(m_level); } edge = 2. / 3.14; // inverse of roughly 1/4 circle numTrixels = 8; for (int i = m_level; i--;) { numTrixels *= 4; edge *= 2.0; } edge = 1.0 / edge; // roughly length of one edge (radians) edge10 = edge / 10.0; eps = 1.0e-6; // arbitrary small number magicNum = numTrixels; degree2Rad = 3.1415926535897932385E0 / 180.0; // Allocate MeshBuffers m_meshBuffer = (MeshBuffer **)malloc(sizeof(MeshBuffer *) * numBuffers); if (m_meshBuffer == nullptr) { fprintf(stderr, "Out of memory allocating %d MeshBuffers.\n", numBuffers); exit(0); } for (int i = 0; i < numBuffers; i++) { m_meshBuffer[i] = new MeshBuffer(this); } } HTMesh::~HTMesh() { delete htm; for (BufNum i = 0; i < m_numBuffers; i++) delete m_meshBuffer[i]; free(m_meshBuffer); } Trixel HTMesh::index(double ra, double dec) const { return (Trixel)htm->idByPoint(SpatialVector(ra, dec)) - magicNum; } bool HTMesh::performIntersection(RangeConvex *convex, BufNum bufNum) { if (!validBufNum(bufNum)) return false; convex->setOlevel(m_level); HtmRange range; convex->intersect(htm, &range); HtmRangeIterator iterator(&range); MeshBuffer *buffer = m_meshBuffer[bufNum]; buffer->reset(); while (iterator.hasNext()) { buffer->append((Trixel)iterator.next() - magicNum); } if (buffer->error()) { fprintf(stderr, "%s: trixel overflow.\n", name); return false; }; return true; } // CIRCLE void HTMesh::intersect(double ra, double dec, double radius, BufNum bufNum) { double d = cos(radius * degree2Rad); SpatialConstraint c(SpatialVector(ra, dec), d); RangeConvex convex; convex.add(c); // [ed:RangeConvex::add] if (!performIntersection(&convex, bufNum)) printf("In intersect(%f, %f, %f)\n", ra, dec, radius); } // TRIANGLE void HTMesh::intersect(double ra1, double dec1, double ra2, double dec2, double ra3, double dec3, BufNum bufNum) { if (fabs(ra1 - ra3) + fabs(dec1 - dec3) < eps) return intersect(ra1, dec1, ra2, dec2); else if (fabs(ra1 - ra2) + fabs(dec1 - dec2) < eps) return intersect(ra1, dec1, ra3, dec3); else if (fabs(ra2 - ra3) + fabs(dec2 - dec3) < eps) return intersect(ra1, dec1, ra2, dec2); SpatialVector p1(ra1, dec1); SpatialVector p2(ra2, dec2); SpatialVector p3(ra3, dec3); RangeConvex convex(&p1, &p2, &p3); if (!performIntersection(&convex, bufNum)) printf("In intersect(%f, %f, %f, %f, %f, %f)\n", ra1, dec1, ra2, dec2, ra3, dec3); } // QUADRILATERAL void HTMesh::intersect(double ra1, double dec1, double ra2, double dec2, double ra3, double dec3, double ra4, double dec4, BufNum bufNum) { if (fabs(ra1 - ra4) + fabs(dec1 - dec4) < eps) return intersect(ra2, dec2, ra3, dec3, ra4, dec4); else if (fabs(ra1 - ra2) + fabs(dec1 - dec2) < eps) return intersect(ra2, dec2, ra3, dec3, ra4, dec4); else if (fabs(ra2 - ra3) + fabs(dec2 - dec3) < eps) return intersect(ra1, dec1, ra2, dec2, ra4, dec4); else if (fabs(ra3 - ra4) + fabs(dec3 - dec4) < eps) return intersect(ra1, dec1, ra2, dec2, ra4, dec4); SpatialVector p1(ra1, dec1); SpatialVector p2(ra2, dec2); SpatialVector p3(ra3, dec3); SpatialVector p4(ra4, dec4); RangeConvex convex(&p1, &p2, &p3, &p4); if (!performIntersection(&convex, bufNum)) printf("In intersect(%f, %f, %f, %f, %f, %f, %f, %f)\n", ra1, dec1, ra2, dec2, ra3, dec3, ra4, dec4); } void HTMesh::toXYZ(double ra, double dec, double *x, double *y, double *z) { ra *= degree2Rad; dec *= degree2Rad; double sinRa = sin(ra); double cosRa = cos(ra); double sinDec = sin(dec); double cosDec = cos(dec); *x = cosDec * cosRa; *y = cosDec * sinRa; *z = sinDec; } // Intersect a line segment by forming a very thin triangle to use for the // intersection. Use cross product to ensure we have a perpendicular vector. // LINE void HTMesh::intersect(double ra1, double dec1, double ra2, double dec2, BufNum bufNum) { double x1, y1, z1, x2, y2, z2; //if (ra1 == 0.0 || ra1 == 180.00) ra1 += 0.1; //if (ra2 == 0.0 || ra2 == 180.00) ra2 -= 0.1; //if (dec1 == 0.0 ) dec1 += 0.1; //if (dec2 == 0.0 ) dec2 -= 0.1; // convert to Cartesian. Ugh. toXYZ(ra1, dec1, &x1, &y1, &z1); toXYZ(ra2, dec2, &x2, &y2, &z2); // Check if points are too close double len; len = fabs(x1 - x2); len += fabs(y1 - y2); len += fabs(z1 - z2); if (htmDebug > 0) { printf("htmDebug = %d\n", htmDebug); printf("p1 = (%f, %f, %f)\n", x1, y1, z1); printf("p2 = (%f, %f, %f)\n", x2, y2, z2); printf("edge: %f (radians) %f (degrees)\n", edge, edge / degree2Rad); printf("len : %f (radians) %f (degrees)\n", len, len / degree2Rad); } if (len < edge10) return intersect(ra1, len, bufNum); // Cartesian cross product => perpendicular!. Ugh. double cx = y1 * z2 - z1 * y2; double cy = z1 * x2 - x1 * z2; double cz = x1 * y2 - y1 * x2; if (htmDebug > 0) printf("cp = (%f, %f, %f)\n", cx, cy, cz); double norm = edge10 / (fabs(cx) + fabs(cy) + fabs(cz)); // give it length edge/10 cx *= norm; cy *= norm; cz *= norm; if (htmDebug > 0) printf("cpn = (%f, %f, %f)\n", cx, cy, cz); // add it to (ra1, dec1) cx += x1; cy += y1; cz += z1; if (htmDebug > 0) printf("cpf = (%f, %f, %f)\n", cx, cy, cz); // back to spherical norm = sqrt(cx * cx + cy * cy + cz * cz); double ra0 = atan2(cy, cx) / degree2Rad; double dec0 = asin(cz / norm) / degree2Rad; if (htmDebug > 0) printf("new ra, dec = (%f, %f)\n", ra0, dec0); SpatialVector p1(ra1, dec1); SpatialVector p0(ra0, dec0); SpatialVector p2(ra2, dec2); RangeConvex convex(&p1, &p0, &p2); if (!performIntersection(&convex, bufNum)) printf("In intersect(%f, %f, %f, %f)\n", ra1, dec1, ra2, dec2); } MeshBuffer *HTMesh::meshBuffer(BufNum bufNum) { if (!validBufNum(bufNum)) - return 0; + return nullptr; return m_meshBuffer[bufNum]; } int HTMesh::intersectSize(BufNum bufNum) { if (!validBufNum(bufNum)) return 0; return m_meshBuffer[bufNum]->size(); } void HTMesh::vertices(Trixel id, double *ra1, double *dec1, double *ra2, double *dec2, double *ra3, double *dec3) { SpatialVector v1, v2, v3; htm->nodeVertex(id + magicNum, v1, v2, v3); *ra1 = v1.ra(); *dec1 = v1.dec(); *ra2 = v2.ra(); *dec2 = v2.dec(); *ra3 = v3.ra(); *dec3 = v3.dec(); } diff --git a/kstars/htmesh/HtmRangeIterator.h b/kstars/htmesh/HtmRangeIterator.h index a4741e0b1..6263a5244 100644 --- a/kstars/htmesh/HtmRangeIterator.h +++ b/kstars/htmesh/HtmRangeIterator.h @@ -1,31 +1,31 @@ #ifndef _HTMHANGEITERATOR_H_ #define _HTMHANGEITERATOR_H_ #include class HtmRangeIterator { public: Key next(); char *nextSymbolic(char *buffer); /* User responsible for managing it */ bool hasNext(); HtmRangeIterator(HtmRange *ran) { range = ran; range->reset(); range->getNext(&currange[0], &currange[1]); nextval = currange[0] - 1; getNext(); } protected: HtmRange *range; void getNext(); private: Key nextval; Key currange[2]; /* Low and High */ - HtmRangeIterator() : range(0), nextval(-1) {} + HtmRangeIterator() : range(nullptr), nextval(-1) {} }; #endif diff --git a/kstars/htmesh/SkipList.cpp b/kstars/htmesh/SkipList.cpp index 0de7c519d..076ad1e6a 100644 --- a/kstars/htmesh/SkipList.cpp +++ b/kstars/htmesh/SkipList.cpp @@ -1,332 +1,332 @@ /* File: SkipList.C Author: Bruno Grossniklaus, 13.11.97 Mod: Gyorgy Fekete, Oct-09-2002 Version: 1.0 History: Nov-13-1997; Gro; Version 1.0 Oct-09-2002; JHU; Version 1.1 */ #include // cout #include // setw #include // rand(), drand48() #include "SkipListElement.h" #include "SkipList.h" #include #ifndef HAVE_DRAND48 double drand48() { double result; #ifdef _WIN32 result = static_cast(rand()); result /= RAND_MAX; #else result = static_cast(random()); result /= LONG_MAX; #endif return result; } #endif /* HAVE_DRAND48 */ //////////////////////////////////////////////////////////////////////////////// // get new element level using given probability //////////////////////////////////////////////////////////////////////////////// long getNewLevel(long maxLevel, float probability) { long newLevel = 0; while ((newLevel < maxLevel - 1) && (drand48() < probability)) // fast hack. fix later newLevel++; return (newLevel); } //////////////////////////////////////////////////////////////////////////////// SkipList::SkipList(float probability) : myProbability(probability) { myHeader = new SkipListElement(); // get memory for header element myHeader->setKey(KEY_MAX); myLength = 0; iter = myHeader; } //////////////////////////////////////////////////////////////////////////////// SkipList::~SkipList() { delete myHeader; // free memory for header element } //////////////////////////////////////////////////////////////////////////////// void SkipList::insert(const Key searchKey, const Value value) { int i; long newLevel; SkipListElement *element; SkipListElement *nextElement; SkipListElement update(SKIPLIST_MAXLEVEL); // scan all levels while key < searchKey // starting with header in his level element = myHeader; for (i = myHeader->getLevel(); i >= 0; i--) { nextElement = element->getElement(i); while ((nextElement != NIL) && (nextElement->getKey() < searchKey)) { element = nextElement; nextElement = element->getElement(i); } // save level pointer update.setElement(i, element); } // key is < searchKey element = element->getElement(0); if ((element != NIL) && (element->getKey() == searchKey)) { // key exists. set new value element->setValue(value); } else { // new key. add to list // get new level and fix list level // get new level newLevel = getNewLevel(SKIPLIST_MAXLEVEL, myProbability); if (newLevel > myHeader->getLevel()) { // adjust header level for (i = myHeader->getLevel() + 1; i <= newLevel; i++) { // adjust new pointer of new element update.setElement(i, myHeader); } // set new header level myHeader->setLevel(newLevel); } // make new element [NEW *******] myLength++; element = new SkipListElement(newLevel, searchKey, value); for (i = 0; i <= newLevel; i++) // scan all levels { // set next pointer of new element element->setElement(i, update.getElement(i)->getElement(i)); update.getElement(i)->setElement(i, element); } } } //////////////////////////////////////////////////////////////////////////////// // greatest key less than searchKey.. almost completely, but not // quite entirely unlike a search ... Key SkipList::findMAX(const Key searchKey) const { int i; SkipListElement *element; SkipListElement *nextElement; Key retKey; element = myHeader; for (i = myHeader->getLevel(); i >= 0; i--) { nextElement = element->getElement(i); while ((nextElement != NIL) && (nextElement->getKey() < searchKey)) { element = nextElement; nextElement = element->getElement(i); } } // now nextElement is >= searhcKey // and element is < searchKey // therefore elemnt key is largest key less than current // if this were search: // element=element->getElement(0); // skip to >= if (element != NIL) { retKey = element->getKey(); return retKey == KEY_MAX ? (-KEY_MAX) : retKey; } else { return ((Key)KEY_MAX); } } // smallest greater than searchKey.. almost completely, but not // quite entirely unlike a search ... Key SkipList::findMIN(const Key searchKey) const { int i; SkipListElement *element(myHeader); - SkipListElement *nextElement = 0; + SkipListElement *nextElement = nullptr; Key retKey; for (i = myHeader->getLevel(); i >= 0; i--) { nextElement = element->getElement(i); while ((nextElement != NIL) && (nextElement->getKey() <= searchKey)) { element = nextElement; nextElement = element->getElement(i); } } // now nextElement is > searchKey // element is <= , make it > // element = nextElement; if (element != NIL) { retKey = element->getKey(); return retKey == KEY_MAX ? (-KEY_MAX) : retKey; } else { return (Key)KEY_MAX; } } //////////////////////////////////////////////////////////////////////////////// /* Very similar to free, but frees a range of keys from lo to hi, inclusive */ void SkipList::freeRange(const Key loKey, const Key hiKey) { int i; SkipListElement *element; SkipListElement *nextElement; // scan all levels while key < searchKey // starting with header in his level element = myHeader; for (i = myHeader->getLevel(); i >= 0; i--) { nextElement = element->getElement(i); while ((nextElement != NIL) && (nextElement->getKey() < loKey)) { element = nextElement; nextElement = element->getElement(i); } } // key is < loKey element = element->getElement(0); while ((element != NIL) && (element->getKey() <= hiKey)) { nextElement = element->getElement(0); free(element->getKey()); element = nextElement; } } //////////////////////////////////////////////////////////////////////////////// void SkipList::free(const Key searchKey) { int i; SkipListElement *element; SkipListElement *nextElement; SkipListElement update(SKIPLIST_MAXLEVEL); // scan all levels while key < searchKey // starting with header in his level element = myHeader; for (i = myHeader->getLevel(); i >= 0; i--) { nextElement = element->getElement(i); while ((nextElement != NIL) && (nextElement->getKey() < searchKey)) { element = nextElement; nextElement = element->getElement(i); } // save level pointer update.setElement(i, element); } // key is < searchKey element = element->getElement(0); // if key exists if ((element != NIL) && (element->getKey() == searchKey)) { for (i = 0; i <= myHeader->getLevel(); i++) // save next pointers { if (update.getElement(i)->getElement(i) == element) { update.getElement(i)->setElement(i, element->getElement(i)); } } // free memory of element delete element; myLength--; // set new header level while ((myHeader->getLevel() > 0) && (myHeader->getElement(myHeader->getLevel()) == NIL)) { myHeader->setLevel(myHeader->getLevel() - 1); } } } //// STATISTICS on skiplist void SkipList::stat() { int count = 0; SkipListElement *element; SkipListElement *nextElement; element = myHeader; nextElement = element->getElement(0); while ((nextElement != NIL)) { count++; element = nextElement; nextElement = element->getElement(0); } std::cout << "Have number of elements ... " << count << std::endl; std::cout << "Size ..................... " << myLength << std::endl; { int *hist; hist = new int[20]; int i; long totalPointers, usedPointers; for (i = 0; i < 20; i++) { hist[i] = 0; } element = myHeader; count = 0; nextElement = element->getElement(0); while ((nextElement != NIL)) { count++; hist[nextElement->getLevel()]++; element = nextElement; nextElement = element->getElement(0); } // // There are SKIPLIST_MAXLEVEL * count available pointers // totalPointers = SKIPLIST_MAXLEVEL * count; usedPointers = 0; // // of these every node that is not at the max level wastes some // for (i = 0; i < 20; i++) { if (hist[i] > 0) std::cout << std::setw(2) << i << ": " << std::setw(6) << hist[i] << std::endl; usedPointers += hist[i] * (1 + i); } std::cout << "Used pointers " << usedPointers << std::endl; std::cout << "Total pointers " << totalPointers << " efficiency = " << (double)usedPointers / (double)totalPointers << std::endl; delete[] hist; } return; } diff --git a/kstars/htmesh/SkipListElement.cpp b/kstars/htmesh/SkipListElement.cpp index 1a2636ded..ac5482e65 100644 --- a/kstars/htmesh/SkipListElement.cpp +++ b/kstars/htmesh/SkipListElement.cpp @@ -1,49 +1,49 @@ /* File: SkipListElement.C Author: Bruno Grossniklaus, 13.11.97 Version: 1.0 History: 13.11.97; Gro; Version 1.0 */ #include // cout #include "SkipListElement.h" //////////////////////////////////////////////////////////////////////////////// SkipListElement::SkipListElement(long level, Key key, Value value) : myLevel(level), myKey(key), myValue(value) { for (int i = 0; i < SKIPLIST_MAXLEVEL; i++) - myNext[i] = 0; + myNext[i] = nullptr; } //////////////////////////////////////////////////////////////////////////////// SkipListElement *SkipListElement::getElement(long level) { if (level > myLevel) { std::cerr << "Error in :" << "SkipListElement::getElement() level:"; std::cerr << level << ", my level:" << myLevel << ", max level: " << SKIPLIST_MAXLEVEL << std::endl; return (this); } else { return (myNext[level]); } } //////////////////////////////////////////////////////////////////////////////// void SkipListElement::setElement(long level, SkipListElement *element) { if (level > myLevel) { std::cerr << "Error in :" << "SkipListElement::setElement() level:"; std::cerr << level << ", my level:" << myLevel << ", max level: " << SKIPLIST_MAXLEVEL << std::endl; } else { myNext[level] = element; } } diff --git a/kstars/htmesh/SpatialException.h b/kstars/htmesh/SpatialException.h index 9c9630754..c600adb15 100644 --- a/kstars/htmesh/SpatialException.h +++ b/kstars/htmesh/SpatialException.h @@ -1,169 +1,169 @@ //# Filename: SpatialException.h //# //# Author: Peter Z Kunszt based on John Doug Reynolds'code //# //# Date: March 1998 //# //# Copyright (C) 2000 Peter Z. Kunszt, Alex S. Szalay, Aniruddha R. Thakar //# The Johns Hopkins University //# //# Modification History: //# //# Oct 18, 2001 : Dennis C. Dinge -- Replaced ValVec with std::vector //# #ifndef _SpatialException_h #define _SpatialException_h #include "SpatialGeneral.h" /** HTM SpatialIndex Exception base class This is the base class for all Science Archive exceptions. It may be used as a generic exception, but programmers are encouraged to use the more specific derived classes. Note that all Spatial exceptions are also Standard Library exceptions by inheritance. */ class LINKAGE SpatialException { public: /** Default and explicit constructor. The default constructor supplies a generic message indicating the exception type. The explicit constructor sets the message to a copy of the provided string. This behavior is shared by all derived classes. */ - SpatialException(const char *what = 0, int defIndex = 1) throw(); + SpatialException(const char *what = nullptr, int defIndex = 1) throw(); /** Standard constructor. The message is assembled from copies of the two component strings. The first indicates where in the program the exception was thrown, and the second indicates why. The null pointer is used to select standard components according to the type of the exception. This behavior is shared by all derived classes. */ SpatialException(const char *context, const char *because, int defIndex = 1) throw(); /// Copy constructor. SpatialException(const SpatialException &) throw(); /// Assignment operator. SpatialException &operator=(const SpatialException &) throw(); /// Destructor. virtual ~SpatialException() throw(); /// Returns the message as set during construction. virtual const char *what() const throw(); /// return string length also for null strings int slen(const char *) const; /// deallocate string void clear(); /// default error string static const char *defaultstr[]; protected: /// error string to assemble char *str_; }; /** SpatialException thrown by unimplemented functions. This Exception should be thrown wherever important functionality has been left temporarily unimplemented. Typically this exception will apply to an entire function. */ class LINKAGE SpatialUnimplemented : public SpatialException { public: /// Default and explicit constructors. - SpatialUnimplemented(const char *what = 0) throw(); + SpatialUnimplemented(const char *what = nullptr) throw(); /// Standard constructor. SpatialUnimplemented(const char *context, const char *because) throw(); /// Copy constructor. SpatialUnimplemented(const SpatialUnimplemented &) throw(); }; /** SpatialException thrown on operational failure. This Exception should be thrown when an operation fails unexpectedly. A special constructor is provided for assembling the message from the typical components: program context, operation name, resource name, and explanation. As usual, any component may be left out by specifying the null pointer. */ class LINKAGE SpatialFailure : public SpatialException { public: /// Default and explicit constructors. - SpatialFailure(const char *what = 0) throw(); + SpatialFailure(const char *what = nullptr) throw(); /// Standard constructor. SpatialFailure(const char *context, const char *because) throw(); /// Special constructor. - SpatialFailure(const char *context, const char *operation, const char *resource, const char *because = 0) throw(); + SpatialFailure(const char *context, const char *operation, const char *resource, const char *because = nullptr) throw(); /// Copy constructor. SpatialFailure(const SpatialFailure &) throw(); }; /** SpatialException thrown on violation of array bounds. This Exception should be thrown on detection of an attempt to access elements beyond the boundaries of an array. A special constructor is provided for assembling the message from the typical components: program context, array name, violated boundary, and violating index. */ class LINKAGE SpatialBoundsError : public SpatialException { public: /// Default and explicit constructors. - SpatialBoundsError(const char *what = 0) throw(); + SpatialBoundsError(const char *what = nullptr) throw(); /** Standard constructor. If limit and index are -1, both are considered unknown. Note that the upper limit of a zero-offset array is not the same as the number of elements. */ SpatialBoundsError(const char *context, const char *array, int32 limit = -1, int32 index = -1) throw(); /// Copy constructor. SpatialBoundsError(const SpatialBoundsError &) throw(); }; /** SpatialException thrown on violation of interface protocols. This Exception should be thrown when a program, class, or function interface requirement is breached. Specifically, this includes improper usage and invalid arguments. For the latter, a special constructor is provided for assembling the message from the typical components: program context, argument name, and explanation. */ class LINKAGE SpatialInterfaceError : public SpatialException { public: /// Default and explicit constructors. - SpatialInterfaceError(const char *what = 0) throw(); + SpatialInterfaceError(const char *what = nullptr) throw(); /// Standard constructor. SpatialInterfaceError(const char *context, const char *because) throw(); /// Special constructor. SpatialInterfaceError(const char *context, const char *argument, const char *because) throw(); /// Copy constructor. SpatialInterfaceError(const SpatialInterfaceError &) throw(); }; #endif /* _SpatialException_h */ diff --git a/kstars/htmesh/SpatialIndex.cpp b/kstars/htmesh/SpatialIndex.cpp index 4ff13c962..548e82463 100644 --- a/kstars/htmesh/SpatialIndex.cpp +++ b/kstars/htmesh/SpatialIndex.cpp @@ -1,613 +1,613 @@ //# Filename: SpatialIndex.cpp //# //# The SpatialIndex class is defined here. //# //# Author: Peter Z. Kunszt based on A. Szalay's code //# //# Date: October 15, 1998 //# //# Copyright (C) 2000 Peter Z. Kunszt, Alex S. Szalay, Aniruddha R. Thakar //# The Johns Hopkins University //# //# Modification History: //# //# Oct 18, 2001 : Dennis C. Dinge -- Replaced ValVec with std::vector //# Jul 25, 2002 : Gyorgy Fekete -- Added pointById() //# #include "SpatialIndex.h" #include "SpatialException.h" #ifdef _WIN32 #include #else #ifdef __APPLE__ #include #else #include #endif #endif #include #include // =========================================================================== // // Macro definitions for readability // // =========================================================================== #define N(x) nodes_[(x)] #define V(x) vertices_[nodes_[index].v_[(x)]] #define IV(x) nodes_[index].v_[(x)] #define W(x) vertices_[nodes_[index].w_[(x)]] #define IW(x) nodes_[index].w_[(x)] #define ICHILD(x) nodes_[index].childID_[(x)] #define IV_(x) nodes_[index_].v_[(x)] #define IW_(x) nodes_[index_].w_[(x)] #define ICHILD_(x) nodes_[index_].childID_[(x)] #define IOFFSET 9 // =========================================================================== // // Member functions for class SpatialIndex // // =========================================================================== /////////////CONSTRUCTOR////////////////////////////////// // SpatialIndex::SpatialIndex(size_t maxlevel, size_t buildlevel) : maxlevel_(maxlevel), buildlevel_((buildlevel == 0 || buildlevel > maxlevel) ? maxlevel : buildlevel) { size_t nodes, vertices; vMax(&nodes, &vertices); layers_.resize(buildlevel_ + 1); // allocate enough space already nodes_.resize(nodes + 1); // allocate space for all nodes vertices_.resize(vertices + 1); // allocate space for all vertices N(0).index_ = 0; // initialize invalid node // initialize first layer layers_[0].level_ = 0; layers_[0].nVert_ = 6; layers_[0].nNode_ = 8; layers_[0].nEdge_ = 12; layers_[0].firstIndex_ = 1; layers_[0].firstVertex_ = 0; // set the first 6 vertices float64 v[6][3] = { { 0.0L, 0.0L, 1.0L }, // 0 { 1.0L, 0.0L, 0.0L }, // 1 { 0.0L, 1.0L, 0.0L }, // 2 { -1.0L, 0.0L, 0.0L }, // 3 { 0.0L, -1.0L, 0.0L }, // 4 { 0.0L, 0.0L, -1.0L } // 5 }; for (int i = 0; i < 6; i++) vertices_[i].set(v[i][0], v[i][1], v[i][2]); // create the first 8 nodes - index 1 through 8 index_ = 1; newNode(1, 5, 2, 8, 0); // S0 newNode(2, 5, 3, 9, 0); // S1 newNode(3, 5, 4, 10, 0); // S2 newNode(4, 5, 1, 11, 0); // S3 newNode(1, 0, 4, 12, 0); // N0 newNode(4, 0, 3, 13, 0); // N1 newNode(3, 0, 2, 14, 0); // N2 newNode(2, 0, 1, 15, 0); // N3 // loop through buildlevel steps, and build the nodes for each layer size_t pl = 0; size_t level = buildlevel_; while (level-- > 0) { SpatialEdge edge(*this, pl); edge.makeMidPoints(); makeNewLayer(pl); ++pl; } sortIndex(); } /////////////NODEVERTEX/////////////////////////////////// // nodeVertex: return the vectors of the vertices, based on the ID // void SpatialIndex::nodeVertex(const uint64 id, SpatialVector &v0, SpatialVector &v1, SpatialVector &v2) const { if (buildlevel_ == maxlevel_) { uint32 idx = (uint32)id - leaves_ + IOFFSET; // -jbb: Fix segfault. See "idx =" below. v0 = vertices_[nodes_[idx].v_[0]]; v1 = vertices_[nodes_[idx].v_[1]]; v2 = vertices_[nodes_[idx].v_[2]]; return; } // buildlevel < maxlevel // get the id of the stored leaf that we are in // and get the vertices of the node we want uint64 sid = id >> ((maxlevel_ - buildlevel_) * 2); uint32 idx = (uint32)(sid - storedleaves_ + IOFFSET); v0 = vertices_[nodes_[idx].v_[0]]; v1 = vertices_[nodes_[idx].v_[1]]; v2 = vertices_[nodes_[idx].v_[2]]; // loop therough additional levels, // pick the correct triangle accordingly, storing the // vertices in v1,v2,v3 for (uint32 i = buildlevel_ + 1; i <= maxlevel_; i++) { uint64 j = (id >> ((maxlevel_ - i) * 2)) & 3; SpatialVector w0 = v1 + v2; w0.normalize(); SpatialVector w1 = v0 + v2; w1.normalize(); SpatialVector w2 = v1 + v0; w2.normalize(); switch (j) { case 0: v1 = w2; v2 = w1; break; case 1: v0 = v1; v1 = w0; v2 = w2; break; case 2: v0 = v2; v1 = w1; v2 = w0; break; case 3: v0 = w0; v1 = w1; v2 = w2; break; } } } /////////////MAKENEWLAYER///////////////////////////////// // makeNewLayer: generate a new layer and the nodes in it // void SpatialIndex::makeNewLayer(size_t oldlayer) { uint64 index, id; size_t newlayer = oldlayer + 1; layers_[newlayer].level_ = layers_[oldlayer].level_ + 1; layers_[newlayer].nVert_ = layers_[oldlayer].nVert_ + layers_[oldlayer].nEdge_; layers_[newlayer].nNode_ = 4 * layers_[oldlayer].nNode_; layers_[newlayer].nEdge_ = layers_[newlayer].nNode_ + layers_[newlayer].nVert_ - 2; layers_[newlayer].firstIndex_ = index_; layers_[newlayer].firstVertex_ = layers_[oldlayer].firstVertex_ + layers_[oldlayer].nVert_; uint64 ioffset = layers_[oldlayer].firstIndex_; for (index = ioffset; index < ioffset + layers_[oldlayer].nNode_; index++) { id = N(index).id_ << 2; ICHILD(0) = newNode(IV(0), IW(2), IW(1), id++, index); ICHILD(1) = newNode(IV(1), IW(0), IW(2), id++, index); ICHILD(2) = newNode(IV(2), IW(1), IW(0), id++, index); ICHILD(3) = newNode(IW(0), IW(1), IW(2), id, index); } } /////////////NEWNODE////////////////////////////////////// // newNode: make a new node // uint64 SpatialIndex::newNode(size_t v1, size_t v2, size_t v3, uint64 id, uint64 parent) { IV_(0) = v1; // vertex indices IV_(1) = v2; IV_(2) = v3; IW_(0) = 0; // middle point indices IW_(1) = 0; IW_(2) = 0; ICHILD_(0) = 0; // child indices ICHILD_(1) = 0; // index 0 is invalid node. ICHILD_(2) = 0; ICHILD_(3) = 0; N(index_).id_ = id; // set the id N(index_).index_ = index_; // set the index N(index_).parent_ = parent; // set the parent return index_++; } /////////////VMAX///////////////////////////////////////// // vMax: compute the maximum number of vertices for the // polyhedron after buildlevel of subdivisions and // the total number of nodes that we store // also, calculate the number of leaf nodes that we eventually have. // void SpatialIndex::vMax(size_t *nodes, size_t *vertices) { uint64 nv = 6; // initial values uint64 ne = 12; uint64 nf = 8; int32 i = buildlevel_; *nodes = (size_t)nf; while (i-- > 0) { nv += ne; nf *= 4; ne = nf + nv - 2; *nodes += (size_t)nf; } *vertices = (size_t)nv; storedleaves_ = nf; // calculate number of leaves i = maxlevel_ - buildlevel_; while (i-- > 0) nf *= 4; leaves_ = nf; } /////////////SORTINDEX//////////////////////////////////// // sortIndex: sort the index so that the first node is the invalid node // (index 0), the next 8 nodes are the root nodes // and then we put all the leaf nodes in the following block // in ascending id-order. // All the rest of the nodes is at the end. void SpatialIndex::sortIndex() { std::vector oldnodes(nodes_); // create a copy of the node list size_t index; size_t nonleaf; size_t leaf; #define ON(x) oldnodes[(x)] // now refill the nodes_ list according to our sorting. for (index = IOFFSET, leaf = IOFFSET, nonleaf = nodes_.size() - 1; index < nodes_.size(); index++) { if (ON(index).childID_[0] == 0) // childnode { // set leaf into list N(leaf) = ON(index); // set parent's pointer to this leaf for (size_t i = 0; i < 4; i++) { if (N(N(leaf).parent_).childID_[i] == index) { N(N(leaf).parent_).childID_[i] = leaf; break; } } leaf++; } else { // set nonleaf into list from the end // set parent of the children already to this // index, they come later in the list. N(nonleaf) = ON(index); ON(N(nonleaf).childID_[0]).parent_ = nonleaf; ON(N(nonleaf).childID_[1]).parent_ = nonleaf; ON(N(nonleaf).childID_[2]).parent_ = nonleaf; ON(N(nonleaf).childID_[3]).parent_ = nonleaf; // set parent's pointer to this leaf for (size_t i = 0; i < 4; i++) { if (N(N(nonleaf).parent_).childID_[i] == index) { N(N(nonleaf).parent_).childID_[i] = nonleaf; break; } } nonleaf--; } } } //////////////////IDBYNAME///////////////////////////////////////////////// // Translate ascii leaf name to a uint32 // // The following encoding is used: // // The string leaf name has the always the same structure, it begins with // an N or S, indicating north or south cap and then numbers 0-3 follow // indicating which child to descend into. So for a depth-5-index we have // strings like // N012023 S000222 N102302 etc // // Each of the numbers correspond to 2 bits of code (00 01 10 11) in the // uint32. The first two bits are 10 for S and 11 for N. For example // // N 0 1 2 0 2 3 // 11000110001011 = 12683 (dec) // // The leading bits are always 0. // // --- WARNING: This works only up to 15 levels. // (we probably never need more than 7) // uint64 SpatialIndex::idByName(const char *name) { uint64 out = 0, i; uint32 size = 0; - if (name == 0) // null pointer-name + if (name == nullptr) // null pointer-name throw SpatialFailure("SpatialIndex:idByName:no name given"); if (name[0] != 'N' && name[0] != 'S') // invalid name throw SpatialFailure("SpatialIndex:idByName:invalid name", name); size = strlen(name); // determine string length // at least size-2 required, don't exceed max if (size < 2) throw SpatialFailure("SpatialIndex:idByName:invalid name - too short ", name); if (size > HTMNAMEMAX) throw SpatialFailure("SpatialIndex:idByName:invalid name - too long ", name); for (i = size - 1; i > 0; i--) // set bits starting from the end { if (name[i] > '3' || name[i] < '0') // invalid name throw SpatialFailure("SpatialIndex:idByName:invalid name digit ", name); out += (uint64(name[i] - '0') << 2 * (size - i - 1)); } i = 2; // set first pair of bits, first bit always set if (name[0] == 'N') i++; // for north set second bit too out += (i << (2 * size - 2)); /************************ // This code may be used later for hashing ! if(size==2)out -= 8; else { size -= 2; uint32 offset = 0, level4 = 8; for(i = size; i > 0; i--) { // calculate 4 ^ (level-1), level = size-2 offset += level4; level4 *= 4; } out -= level4 - offset; } **************************/ return out; } //////////////////NAMEBYID///////////////////////////////////////////////// // Translate uint32 to an ascii leaf name // // The encoding described above may be decoded again using the following // procedure: // // * Traverse the uint32 from left to right. // * Find the first 'true' bit. // * The first pair gives N (11) or S (10). // * The subsequent bit-pairs give the numbers 0-3. // char *SpatialIndex::nameById(uint64 id, char *name) { uint32 size = 0, i; #ifdef _WIN32 uint64 IDHIGHBIT = 1; uint64 IDHIGHBIT2 = 1; IDHIGHBIT = IDHIGHBIT << 63; IDHIGHBIT2 = IDHIGHBIT2 << 62; #endif /************* // This code might be useful for hashing later !! // calculate the level (i.e. 8*4^level) and add it to the id: uint32 level=0, level4=8, offset=8; while(id >= offset) { if(++level > 13) { ok = false; offset = 0; break; }// level too deep level4 *= 4; offset += level4; } id += 2 * level4 - offset; **************/ // determine index of first set bit for (i = 0; i < IDSIZE; i += 2) { if ((id << i) & IDHIGHBIT) break; if ((id << i) & IDHIGHBIT2) // invalid id throw SpatialFailure("SpatialIndex:nameById: invalid ID"); } if (id == 0) throw SpatialFailure("SpatialIndex:nameById: invalid ID"); size = (IDSIZE - i) >> 1; // allocate characters if (!name) name = new char[size + 1]; // fill characters starting with the last one for (i = 0; i < size - 1; i++) name[size - i - 1] = '0' + char((id >> i * 2) & 3); // put in first character if ((id >> (size * 2 - 2)) & 1) { name[0] = 'N'; } else { name[0] = 'S'; } name[size] = 0; // end string return name; } //////////////////POINTBYID//////////////////////////////////////////////// // Find a vector for the leaf node given by its ID // void SpatialIndex::pointById(SpatialVector &vec, uint64 ID) const { // uint64 index; float64 center_x, center_y, center_z, sum; char name[HTMNAMEMAX]; SpatialVector v0, v1, v2; // this->nodeVertex(ID, v0, v1, v2); nameById(ID, name); /* I started to go this way until I discovered nameByID... Some docs would be nice for this switch(name[1]){ case '0': index = name[0] == 'S' ? 1 : 5; break; case '1': index = name[0] == 'S' ? 2 : 6; break; case '2': index = name[0] == 'S' ? 3 : 7; break; case '3': index = name[0] == 'S' ? 4 : 8; break; } */ // cerr << "---------- Point by id: " << name << endl; // v0.show(); v1.show(); v2.show(); center_x = v0.x_ + v1.x_ + v2.x_; center_y = v0.y_ + v1.y_ + v2.y_; center_z = v0.z_ + v1.z_ + v2.z_; sum = center_x * center_x + center_y * center_y + center_z * center_z; sum = sqrt(sum); center_x /= sum; center_y /= sum; center_z /= sum; vec.x_ = center_x; vec.y_ = center_y; vec.z_ = center_z; // I don't want it nomralized or radec to be set, // cerr << " - - - - " << endl; // vec.show(); // cerr << "---------- Point by id Retuning" << endl; } //////////////////IDBYPOINT//////////////////////////////////////////////// // Find a leaf node where a vector points to // uint64 SpatialIndex::idByPoint(const SpatialVector &v) const { uint64 index; // start with the 8 root triangles, find the one which v points to for (index = 1; index <= 8; index++) { if ((V(0) ^ V(1)) * v < -gEpsilon) continue; if ((V(1) ^ V(2)) * v < -gEpsilon) continue; if ((V(2) ^ V(0)) * v < -gEpsilon) continue; break; } // loop through matching child until leaves are reached while (ICHILD(0) != 0) { uint64 oldindex = index; for (size_t i = 0; i < 4; i++) { index = nodes_[oldindex].childID_[i]; if ((V(0) ^ V(1)) * v < -gEpsilon) continue; if ((V(1) ^ V(2)) * v < -gEpsilon) continue; if ((V(2) ^ V(0)) * v < -gEpsilon) continue; break; } } // return if we have reached maxlevel if (maxlevel_ == buildlevel_) return N(index).id_; // from now on, continue to build name dynamically. // until maxlevel_ levels depth, continue to append the // correct index, build the index on the fly. char name[HTMNAMEMAX]; nameById(N(index).id_, name); size_t len = strlen(name); SpatialVector v0 = V(0); SpatialVector v1 = V(1); SpatialVector v2 = V(2); size_t level = maxlevel_ - buildlevel_; while (level--) { SpatialVector w0 = v1 + v2; w0.normalize(); SpatialVector w1 = v0 + v2; w1.normalize(); SpatialVector w2 = v1 + v0; w2.normalize(); if (isInside(v, v0, w2, w1)) { name[len++] = '0'; v1 = w2; v2 = w1; continue; } else if (isInside(v, v1, w0, w2)) { name[len++] = '1'; v0 = v1; v1 = w0; v2 = w2; continue; } else if (isInside(v, v2, w1, w0)) { name[len++] = '2'; v0 = v2; v1 = w1; v2 = w0; continue; } else if (isInside(v, w0, w1, w2)) { name[len++] = '3'; v0 = w0; v1 = w1; v2 = w2; continue; } } name[len] = '\0'; return idByName(name); } //////////////////ISINSIDE///////////////////////////////////////////////// // Test whether a vector is inside a triangle. Input triangle has // to be sorted in a counter-clockwise direction. // bool SpatialIndex::isInside(const SpatialVector &v, const SpatialVector &v0, const SpatialVector &v1, const SpatialVector &v2) const { if ((v0 ^ v1) * v < -gEpsilon) return false; if ((v1 ^ v2) * v < -gEpsilon) return false; if ((v2 ^ v0) * v < -gEpsilon) return false; return true; } diff --git a/kstars/htmesh/SpatialIndex.h b/kstars/htmesh/SpatialIndex.h index 8856a89d2..70ac69b32 100644 --- a/kstars/htmesh/SpatialIndex.h +++ b/kstars/htmesh/SpatialIndex.h @@ -1,150 +1,150 @@ #ifndef _SpatialIndex_h #define _SpatialIndex_h //# Filename: SpatialIndex.h //# //# SpatialIndex is the class for the sky indexing routines. //# //# Author: Peter Z. Kunszt, based on A. Szalay s code //# //# Date: October 15, 1998 //# //# Copyright (C) 2000 Peter Z. Kunszt, Alex S. Szalay, Aniruddha R. Thakar //# The Johns Hopkins University //# Modification History: //# //# Oct 18, 2001 : Dennis C. Dinge -- Replaced ValVec with std::vector //# Sept 9, 2002 : Gyorgy Fekete -- added setMaxlevel() //# #include #include #include #include #include #include #include //######################################################################## //# //# Spatial Index class //# /** @class SpatialIndex SpatialIndex is a quad tree of spherical triangles. The tree is built in the following way: Start out with 8 triangles on the sphere using the 3 main circles to determine them. Then, every triangle can be decomposed into 4 new triangles by drawing main circles between midpoints of its edges:
 
 .                            /\
 .                           /  \
 .                          /____\
 .                         /\    /\
 .                        /  \  /  \
 .                       /____\/____\
 
 
This is how the quad tree is built up to a certain level by decomposing every triangle again and again. */ class LINKAGE SpatialIndex { public: /** Constructor. Give the level of the index and optionally the level to build - i.e. the depth to keep in memory. if maxlevel - buildlevel > 0 , that many levels are generated on the fly each time the index is called. */ SpatialIndex(size_t maxlevel, size_t buildlevel = 5); /// NodeName conversion to integer ID static uint64 idByName(const char *); /** int32 conversion to a string (name of database). WARNING: if name is already allocated, a size of at least 17 is required. The conversion is done by directly calculating the name from a number. To calculate the name of a certain level, the mechanism is that the name is given by (#of nodes in that level) + (id of node). So for example, for the first level, there are 8 nodes, and we get the names from numbers 8 through 15 giving S0,S1,S2,S3,N0,N1,N2,N3. The order is always ascending starting from S0000.. to N3333... */ - static char *nameById(uint64 ID, char *name = 0); + static char *nameById(uint64 ID, char *name = nullptr); /** find the vector to the centroid of a triangle represented by the ID */ void pointById(SpatialVector &vector, uint64 ID) const; /** find a node by giving a vector. The ID of the node is returned. */ uint64 idByPoint(const SpatialVector &vector) const; /// return the actual vertex vectors void nodeVertex(const uint64 id, SpatialVector &v1, SpatialVector &v2, SpatialVector &v3) const; private: // STRUCTURES struct Layer { size_t level_; // layer level size_t nVert_; // number of vertices in this layer size_t nNode_; // number of nodes size_t nEdge_; // number of edges uint64 firstIndex_; // index of first node of this layer size_t firstVertex_; // index of first vertex of this layer }; struct QuadNode { uint64 index_; // its own index size_t v_[3]; // The three vertex vector indices size_t w_[3]; // The three middlepoint vector indices uint64 childID_[4]; // ids of children uint64 parent_; // id of the parent node (needed for sorting) uint64 id_; // numeric id -> name }; // FUNCTIONS // insert a new node_[] into the list. The vertex indices are given by // v1,v2,v3 and the id of the node is set. uint64 newNode(size_t v1, size_t v2, size_t v3, uint64 id, uint64 parent); // make new nodes in a new layer. void makeNewLayer(size_t oldlayer); // return the total number of nodes and vertices void vMax(size_t *nodes, size_t *vertices); // sort the index so that the leaf nodes are at the beginning void sortIndex(); // Test whether a vector v is inside a triangle v0,v1,v2. Input // triangle has to be sorted in a counter-clockwise direction. bool isInside(const SpatialVector &v, const SpatialVector &v0, const SpatialVector &v1, const SpatialVector &v2) const; // VARIABLES size_t maxlevel_; // the depth of the Layer size_t buildlevel_; // the depth of the Layer stored uint64 leaves_; // number of leaf nodes uint64 storedleaves_; // number of stored leaf nodes std::vector nodes_; // the array of nodes std::vector layers_; // array of layers std::vector vertices_; uint64 index_; // the current index_ of vertices friend class SpatialEdge; friend class SpatialConvex; friend class RangeConvex; }; #endif diff --git a/kstars/kspopupmenu.cpp b/kstars/kspopupmenu.cpp index 10c817240..f55567992 100644 --- a/kstars/kspopupmenu.cpp +++ b/kstars/kspopupmenu.cpp @@ -1,642 +1,642 @@ /*************************************************************************** kspopupmenu.cpp - K Desktop Planetarium ------------------- begin : Sat Feb 27 2003 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 "kspopupmenu.h" #include "config-kstars.h" #include "kstars.h" #include "kstarsdata.h" #include "skymap.h" #include "skyobjects/skyobject.h" #include "skyobjects/starobject.h" #include "skyobjects/trailobject.h" #include "skyobjects/deepskyobject.h" #include "skyobjects/ksmoon.h" #include "skyobjects/satellite.h" #include "skyobjects/supernova.h" #include "skycomponents/constellationboundarylines.h" #include "skycomponents/flagcomponent.h" #include "skycomponents/skymapcomposite.h" #include "tools/whatsinteresting/wiview.h" #include "observinglist.h" #ifdef HAVE_INDI #include "indi/indilistener.h" #include "indi/guimanager.h" #include "indi/driverinfo.h" #include "indi/indistd.h" #include "indi/indidevice.h" #include "indi/indigroup.h" #include "indi/indiproperty.h" #include "indi/indielement.h" #include #endif #include #include #include namespace { // Convert magnitude to string representation for QLabel QString magToStr(double m) { if (m > -30 && m < 90) return QString("%1m").arg(m, 0, 'f', 2); else return QString(); } // Return object name QString getObjectName(SkyObject *obj) { // FIXME: make logic less convoluted. if (obj->longname() != obj->name()) // Object has proper name { return obj->translatedLongName() + ", " + obj->translatedName(); } else { if (!obj->translatedName2().isEmpty()) return obj->translatedName() + ", " + obj->translatedName2(); else return obj->translatedName(); } } // String representation for rise/set time of object. If object // doesn't rise/set returns descriptive string. // // Second parameter choose between raise and set. 'true' for // raise, 'false' for set. QString riseSetTimeLabel(SkyObject *o, bool isRaise) { KStarsData *data = KStarsData::Instance(); QTime t = o->riseSetTime(data->ut(), data->geo(), isRaise); if (t.isValid()) { //We can round to the nearest minute by simply adding 30 seconds to the time. QString time = QLocale().toString(t.addSecs(30), QLocale::ShortFormat); return isRaise ? i18n("Rise time: %1", time) : i18nc("the time at which an object falls below the horizon", "Set time: %1", time); } if (o->alt().Degrees() > 0) return isRaise ? i18n("No rise time: Circumpolar") : i18n("No set time: Circumpolar"); else return isRaise ? i18n("No rise time: Never rises") : i18n("No set time: Never rises"); } // String representation for transit time for object QString transitTimeLabel(SkyObject *o) { KStarsData *data = KStarsData::Instance(); QTime t = o->transitTime(data->ut(), data->geo()); if (t.isValid()) //We can round to the nearest minute by simply adding 30 seconds to the time. return i18n("Transit time: %1", QLocale().toString(t.addSecs(30), QLocale::ShortFormat)); else return "--:--"; } } KSPopupMenu::KSPopupMenu() - : QMenu(KStars::Instance()), m_CurrentFlagIdx(-1), m_EditActionMapping(0), m_DeleteActionMapping(0) + : QMenu(KStars::Instance()), m_CurrentFlagIdx(-1), m_EditActionMapping(nullptr), m_DeleteActionMapping(nullptr) { } KSPopupMenu::~KSPopupMenu() { if (m_EditActionMapping) { delete m_EditActionMapping; } if (m_DeleteActionMapping) { delete m_DeleteActionMapping; } } void KSPopupMenu::createEmptyMenu(SkyPoint *nullObj) { KStars *ks = KStars::Instance(); SkyObject o(SkyObject::TYPE_UNKNOWN, nullObj->ra(), nullObj->dec()); o.setAlt(nullObj->alt()); o.setAz(nullObj->az()); initPopupMenu(&o, i18n("Empty sky"), QString(), QString(), false, false); addAction(i18nc("Sloan Digital Sky Survey", "Show SDSS Image"), ks->map(), SLOT(slotSDSS())); addAction(i18nc("Digitized Sky Survey", "Show DSS Image"), ks->map(), SLOT(slotDSS())); } void KSPopupMenu::slotEditFlag() { if (m_CurrentFlagIdx != -1) { KStars::Instance()->map()->slotEditFlag(m_CurrentFlagIdx); } } void KSPopupMenu::slotDeleteFlag() { if (m_CurrentFlagIdx != -1) { KStars::Instance()->map()->slotDeleteFlag(m_CurrentFlagIdx); } } void KSPopupMenu::slotEditFlag(QAction *action) { int idx = m_EditActionMapping->value(action, -1); if (idx == -1) { return; } else { KStars::Instance()->map()->slotEditFlag(idx); } } void KSPopupMenu::slotDeleteFlag(QAction *action) { int idx = m_DeleteActionMapping->value(action, -1); if (idx == -1) { return; } else { KStars::Instance()->map()->slotDeleteFlag(idx); } } void KSPopupMenu::createStarMenu(StarObject *star) { KStars *ks = KStars::Instance(); //Add name, rise/set time, center/track, and detail-window items QString name; if (star->name() != "star") { name = star->translatedLongName(); } else { if (star->getHDIndex()) { name = QString("HD%1").arg(QString::number(star->getHDIndex())); } else { // FIXME: this should be some catalog name too name = "Star"; } } initPopupMenu(star, name, i18n("star"), i18n("%1m, %2", star->mag(), star->sptype())); //If the star is named, add custom items to popup menu based on object's ImageList and InfoList if (star->name() != "star") { addLinksToMenu(star); } else { addAction(i18nc("Sloan Digital Sky Survey", "Show SDSS Image"), ks->map(), SLOT(slotSDSS())); addAction(i18nc("Digitized Sky Survey", "Show DSS Image"), ks->map(), SLOT(slotDSS())); } } void KSPopupMenu::createDeepSkyObjectMenu(DeepSkyObject *obj) { QString name = getObjectName(obj); QString typeName = obj->typeName(); // FIXME: information about angular sizes should be added. QString info = magToStr(obj->mag()); initPopupMenu(obj, name, typeName, info); addLinksToMenu(obj); } void KSPopupMenu::createPlanetMenu(SkyObject *p) { QString info = magToStr(p->mag()); QString type = i18n("Solar system object"); ; initPopupMenu(p, p->translatedName(), type, info); addLinksToMenu(p, false); //don't offer DSS images for planets } void KSPopupMenu::createMoonMenu(KSMoon *moon) { QString info = QString("%1, %2").arg(magToStr(moon->mag()), moon->phaseName()); initPopupMenu(moon, moon->translatedName(), QString(), info); addLinksToMenu(moon, false); //don't offer DSS images for planets } void KSPopupMenu::createSatelliteMenu(Satellite *satellite) { KStars *ks = KStars::Instance(); QString velocity, altitude, range; velocity.setNum(satellite->velocity()); altitude.setNum(satellite->altitude()); range.setNum(satellite->range()); clear(); addFancyLabel(satellite->name()); addFancyLabel(satellite->id()); addFancyLabel(i18n("satellite")); addFancyLabel(KStarsData::Instance()->skyComposite()->constellationBoundary()->constellationName(satellite)); addSeparator(); addFancyLabel(i18n("Velocity: %1 km/s", velocity), -2); addFancyLabel(i18n("Altitude: %1 km", altitude), -2); addFancyLabel(i18n("Range: %1 km", range), -2); addSeparator(); //Insert item for centering on object addAction(i18n("Center && Track"), ks->map(), SLOT(slotCenter())); //Insert item for measuring distances //FIXME: add key shortcut to menu items properly! addAction(i18n("Angular Distance To... ["), ks->map(), SLOT(slotBeginAngularDistance())); addAction(i18n("Starhop from here to... "), ks->map(), SLOT(slotBeginStarHop())); //Insert "Add/Remove Label" item if (ks->map()->isObjectLabeled(satellite)) addAction(i18n("Remove Label"), ks->map(), SLOT(slotRemoveObjectLabel())); else addAction(i18n("Attach Label"), ks->map(), SLOT(slotAddObjectLabel())); } void KSPopupMenu::createSupernovaMenu(Supernova *supernova) { QString name = supernova->name(); float mag = supernova->mag(); float z = supernova->getRedShift(); QString type = supernova->getType(); QString info; if (mag < 99) info += QString("%1m ").arg(mag); info += type; if (z < 99) info += QString(" z: %1").arg(QString::number(z, 'f', 2)); initPopupMenu(supernova, name, i18n("supernova"), info); } void KSPopupMenu::initPopupMenu(SkyObject *obj, const QString &name, const QString &type, QString info, bool showDetails, bool showObsList, bool showFlags) { KStarsData *data = KStarsData::Instance(); SkyMap *map = SkyMap::Instance(); clear(); bool showLabel = (name != i18n("star") && !name.isEmpty()); QString Name = name; if (Name.isEmpty()) Name = i18n("Empty sky"); addFancyLabel(Name); addFancyLabel(type); addFancyLabel(info); addFancyLabel(KStarsData::Instance()->skyComposite()->constellationBoundary()->constellationName(obj)); //Insert Rise/Set/Transit labels SkyObject *o = obj->clone(); addSeparator(); addFancyLabel(riseSetTimeLabel(o, true), -2); addFancyLabel(riseSetTimeLabel(o, false), -2); addFancyLabel(transitTimeLabel(o), -2); addSeparator(); delete o; // Show 'Select this object' item when in object pointing mode and when obj is not empty sky if (map->isInObjectPointingMode() && obj->type() != 21) { addAction(i18n("Select this object"), map, SLOT(slotObjectSelected())); } //Insert item for centering on object addAction(i18n("Center && Track"), map, SLOT(slotCenter())); if (showFlags) { //Insert actions for flag operations initFlagActions(obj); } //Insert item for measuring distances //FIXME: add key shortcut to menu items properly! addAction(i18n("Angular Distance To... ["), map, SLOT(slotBeginAngularDistance())); addAction(i18n("Starhop from here to... "), map, SLOT(slotBeginStarHop())); //Insert item for Showing details dialog if (showDetails) addAction(i18nc("Show Detailed Information Dialog", "Details"), map, SLOT(slotDetail())); //Insert "Add/Remove Label" item if (showLabel) { if (map->isObjectLabeled(obj)) { addAction(i18n("Remove Label"), map, SLOT(slotRemoveObjectLabel())); } else { addAction(i18n("Attach Label"), map, SLOT(slotAddObjectLabel())); } } // Should show observing list if (showObsList) { if (data->observingList()->contains(obj)) addAction(i18n("Remove From Observing WishList"), data->observingList(), SLOT(slotRemoveObject())); else addAction(i18n("Add to Observing WishList"), data->observingList(), SLOT(slotAddObject())); } // Should we show trail actions TrailObject *t = dynamic_cast(obj); if (t) { if (t->hasTrail()) addAction(i18n("Remove Trail"), map, SLOT(slotRemovePlanetTrail())); else addAction(i18n("Add Trail"), map, SLOT(slotAddPlanetTrail())); } addAction(i18n("Simulate eyepiece view"), map, SLOT(slotEyepieceView())); addSeparator(); #ifdef HAVE_XPLANET if (obj->isSolarSystem() && obj->type() != SkyObject::COMET) // FIXME: We now have asteroids -- so should this not be isMajorPlanet() || Pluto? { //QMenu *xplanetSubmenu = new QMenu(); //xplanetSubmenu->setTitle( i18n( "Print Xplanet view" ) ); addAction(i18n("View in XPlanet"), map, SLOT(slotXplanetToWindow())); //xplanetSubmenu->addAction( i18n( "To file..." ), map, SLOT( slotXplanetToFile() ) ); //addMenu( xplanetSubmenu ); } #endif addSeparator(); addINDI(); addAction(i18n("View in What's Interesting"), this, SLOT(slotViewInWI())); } void KSPopupMenu::initFlagActions(SkyObject *obj) { KStars *ks = KStars::Instance(); QList flags = ks->data()->skyComposite()->flags()->getFlagsNearPix(obj, 5); if (flags.size() == 0) { // There is no flag around clicked SkyObject addAction(i18n("Add flag..."), ks->map(), SLOT(slotAddFlag())); } else if (flags.size() == 1) { // There is only one flag around clicked SkyObject addAction(i18n("Edit flag"), this, SLOT(slotEditFlag())); addAction(i18n("Delete flag"), this, SLOT(slotDeleteFlag())); m_CurrentFlagIdx = flags.first(); } else { // There are more than one flags around clicked SkyObject - we need to create submenus QMenu *editMenu = new QMenu(i18n("Edit flag..."), KStars::Instance()); QMenu *deleteMenu = new QMenu(i18n("Delete flag..."), KStars::Instance()); connect(editMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotEditFlag(QAction*))); connect(deleteMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotDeleteFlag(QAction*))); if (m_EditActionMapping) { delete m_EditActionMapping; } if (m_DeleteActionMapping) { delete m_DeleteActionMapping; } m_EditActionMapping = new QHash; m_DeleteActionMapping = new QHash; foreach (int idx, flags) { QIcon flagIcon(QPixmap::fromImage(ks->data()->skyComposite()->flags()->image(idx))); QString flagLabel = ks->data()->skyComposite()->flags()->label(idx); if (flagLabel.size() > 35) { flagLabel = flagLabel.left(35); flagLabel.append("..."); } QAction *editAction = new QAction(flagIcon, flagLabel, ks); editAction->setIconVisibleInMenu(true); editMenu->addAction(editAction); m_EditActionMapping->insert(editAction, idx); QAction *deleteAction = new QAction(flagIcon, flagLabel, ks); deleteAction->setIconVisibleInMenu(true); deleteMenu->addAction(deleteAction); m_DeleteActionMapping->insert(deleteAction, idx); } addMenu(editMenu); addMenu(deleteMenu); } } void KSPopupMenu::addLinksToMenu(SkyObject *obj, bool showDSS) { KStars *ks = KStars::Instance(); QString sURL; QStringList::ConstIterator itList, itTitle, itListEnd; itList = obj->ImageList().constBegin(); itTitle = obj->ImageTitle().constBegin(); itListEnd = obj->ImageList().constEnd(); if (!obj->ImageList().isEmpty()) { QMenu *imageLinkSubMenu = new QMenu(); imageLinkSubMenu->setTitle(i18n("Image Resources")); for (; itList != itListEnd; ++itList) { QString t = QString(*itTitle); sURL = QString(*itList); imageLinkSubMenu->addAction(i18nc("Image/info menu item (should be translated)", t.toLocal8Bit()), ks->map(), SLOT(slotImage())); ++itTitle; } addMenu(imageLinkSubMenu); } if (showDSS) { addAction(i18nc("Sloan Digital Sky Survey", "Show SDSS Image"), ks->map(), SLOT(slotSDSS())); addAction(i18nc("Digitized Sky Survey", "Show DSS Image"), ks->map(), SLOT(slotDSS())); } if (showDSS) addSeparator(); itList = obj->InfoList().constBegin(); itTitle = obj->InfoTitle().constBegin(); itListEnd = obj->InfoList().constEnd(); if (!obj->InfoList().isEmpty()) { QMenu *infoLinkSubMenu = new QMenu(); infoLinkSubMenu->setTitle(i18n("Information Resources")); for (; itList != itListEnd; ++itList) { QString t = QString(*itTitle); sURL = QString(*itList); infoLinkSubMenu->addAction(i18nc("Image/info menu item (should be translated)", t.toLocal8Bit()), ks->map(), SLOT(slotInfo())); ++itTitle; } addMenu(infoLinkSubMenu); } } void KSPopupMenu::addINDI() { #ifdef HAVE_INDI if (INDIListener::Instance()->size() == 0) return; foreach (ISD::GDInterface *gd, INDIListener::Instance()->getDevices()) { INDI::BaseDevice *bd = gd->getBaseDevice(); if (bd == nullptr) continue; //if (bd->isConnected() == false) // continue; QMenu *menuDevice = nullptr; ISD::GDInterface *telescope = nullptr; foreach (INDI::Property *pp, gd->getProperties()) { if (pp->getType() != INDI_SWITCH || INDIListener::Instance()->isStandardProperty(pp->getName()) == false) continue; QSignalMapper *sMapper = new QSignalMapper(this); ISwitchVectorProperty *svp = pp->getSwitch(); for (int i = 0; i < svp->nsp; i++) { if (menuDevice == nullptr) { menuDevice = new QMenu(gd->getDeviceName()); addMenu(menuDevice); } QAction *a = menuDevice->addAction(svp->sp[i].label); ISD::GDSetCommand *cmd = new ISD::GDSetCommand(INDI_SWITCH, pp->getName(), svp->sp[i].name, ISS_ON, this); sMapper->setMapping(a, cmd); connect(a, SIGNAL(triggered()), sMapper, SLOT(map())); if (!strcmp(svp->sp[i].name, "SLEW") || !strcmp(svp->sp[i].name, "SYNC") || !strcmp(svp->sp[i].name, "TRACK")) { telescope = INDIListener::Instance()->getDevice(gd->getDeviceName()); if (telescope) { QSignalMapper *scopeMapper = new QSignalMapper(this); scopeMapper->setMapping(a, INDI_SEND_COORDS); connect(a, SIGNAL(triggered()), scopeMapper, SLOT(map())); connect(scopeMapper, SIGNAL(mapped(int)), telescope, SLOT(runCommand(int))); } } } connect(sMapper, SIGNAL(mapped(QObject*)), gd, SLOT(setProperty(QObject*))); if (menuDevice != nullptr) menuDevice->addSeparator(); } if (telescope != nullptr && menuDevice != nullptr) { menuDevice->addSeparator(); QAction *a = menuDevice->addAction(i18n("Center Crosshair")); QSignalMapper *scopeMapper = new QSignalMapper(this); scopeMapper->setMapping(a, INDI_ENGAGE_TRACKING); connect(a, SIGNAL(triggered()), scopeMapper, SLOT(map())); connect(scopeMapper, SIGNAL(mapped(int)), telescope, SLOT(runCommand(int))); } } #endif } void KSPopupMenu::addFancyLabel(const QString &name, int deltaFontSize) { if (name.isEmpty()) return; QLabel *label = new QLabel("" + name + "", this); label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); if (deltaFontSize != 0) { QFont font = label->font(); font.setPointSize(font.pointSize() + deltaFontSize); label->setFont(font); } QWidgetAction *act = new QWidgetAction(this); act->setDefaultWidget(label); addAction(act); } void KSPopupMenu::slotViewInWI() { if (!KStars::Instance()->map()->clickedObject()) return; if (!KStars::Instance()->isWIVisible()) KStars::Instance()->slotToggleWIView(); KStars::Instance()->wiView()->inspectSkyObject(KStars::Instance()->map()->clickedObject()); } diff --git a/kstars/kstars.cpp b/kstars/kstars.cpp index c5fbd5857..e9957da2b 100644 --- a/kstars/kstars.cpp +++ b/kstars/kstars.cpp @@ -1,622 +1,622 @@ /*************************************************************************** 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/ekosmanager.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) { setWindowTitle(i18n("KStars")); //On OS X, need to launch kdeinit5 so you can get KLauncher and KIOSlave so you can download new data. //Note: You need to make sure the environment variables for KStars are set correctly to get this running properly. #ifdef Q_OS_OSX QProcess *klauncher = new QProcess(this); klauncher->start("kdeinit5"); #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(); new KstarsAdaptor( this); // NOTE the weird case convention, which cannot be changed as the file is generated by the moc. #ifdef Q_OS_OSX 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); dbusCheck.start("launchctl list"); dbusCheck.waitForFinished(); QString output(dbusCheck.readAllStandardOutput()); QString pluginsDir = QDir(QCoreApplication::applicationDirPath() + "/../PlugIns").absolutePath(); QString dbusPlist = pluginsDir + "/dbus/org.freedesktop.dbus-kstars.plist"; if (!output.contains("homebrew.mxcl.dbus") && !output.contains("org.freedesktop.dbus") && QFileInfo(dbusPlist).exists()) { 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(); } } } #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); 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 comand 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 = 0; + KStarsSplash *splash = nullptr; if (doSplash) { - splash = new KStarsSplash(0); + 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 QSqlDatabase::removeDatabase("userdb"); QSqlDatabase::removeDatabase("skydb"); } void KStars::clearCachedFindDialog() { if (m_FindDialog) // dialog is cached { /** Delete findDialog only if it is not opened */ if (m_FindDialog->isHidden()) { delete m_FindDialog; - m_FindDialog = 0; + m_FindDialog = nullptr; DialogIsObsolete = false; } else DialogIsObsolete = true; // dialog was opened so it could not deleted } } 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", QIcon(":/icons/breeze/default/document-encrypt.svg"))); } 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. #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 //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.size() == 0) { return; } else { // If there are no visible FOVs, select first available if (data()->visibleFOVs.size() == 0) { 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()) { QTimer::singleShot(0, Data->clock(), SLOT(manualTick())); } } #ifdef HAVE_CFITSIO FITSViewer *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_INDI EkosManager *KStars::ekosManager() { if (m_EkosManager.isNull()) m_EkosManager = new EkosManager(Options::independentWindowEkos() ? nullptr : this); return m_EkosManager; } #endif void KStars::closeEvent(QCloseEvent *event) { KStars::Closing = true; QWidget::closeEvent(event); } diff --git a/kstars/kstarsactions.cpp b/kstars/kstarsactions.cpp index 40efa97fc..0c1d7158a 100644 --- a/kstars/kstarsactions.cpp +++ b/kstars/kstarsactions.cpp @@ -1,1936 +1,1936 @@ /*************************************************************************** kstarsactions.cpp - K Desktop Planetarium ------------------- begin : Mon Feb 25 2002 copyright : (C) 2002 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. * * * ***************************************************************************/ // This file contains function definitions for Actions declared in kstars.h #include "kstars.h" #include "imageexporter.h" #include "kstarsdata.h" #include "kswizard.h" #include "Options.h" #include "skymap.h" #include "dialogs/exportimagedialog.h" #include "dialogs/finddialog.h" #include "dialogs/focusdialog.h" #include "dialogs/fovdialog.h" #include "dialogs/locationdialog.h" #include "dialogs/timedialog.h" #include "oal/execute.h" #include "options/opsadvanced.h" #include "options/opscatalog.h" #include "options/opscolors.h" #include "options/opsguides.h" #include "options/opssatellites.h" #include "options/opssolarsystem.h" #include "options/opssupernovae.h" #include "printing/printingwizard.h" #include "projections/projector.h" #include "skycomponents/asteroidscomponent.h" #include "skycomponents/cometscomponent.h" #include "skycomponents/satellitescomponent.h" #include "skycomponents/skymapcomposite.h" #include "skycomponents/solarsystemcomposite.h" #include "skycomponents/supernovaecomponent.h" #include "tools/adddeepskyobject.h" #include "tools/altvstime.h" #include "tools/astrocalc.h" #include "tools/eyepiecefield.h" #include "tools/flagmanager.h" #include "tools/horizonmanager.h" #include "tools/observinglist.h" #include "tools/planetviewer.h" #include "tools/scriptbuilder.h" #include "tools/skycalendar.h" #include "tools/wutdialog.h" #include "tools/polarishourangle.h" #include "tools/whatsinteresting/wiequipsettings.h" #include "tools/whatsinteresting/wilpsettings.h" #include "tools/whatsinteresting/wiview.h" #include "hips/hipsmanager.h" #ifdef HAVE_INDI #include #include "indi/telescopewizardprocess.h" #include "indi/opsindi.h" #include "indi/drivermanager.h" #include "indi/guimanager.h" #include "indi/indilistener.h" #endif #ifdef HAVE_CFITSIO #include "fitsviewer/fitsviewer.h" #include "fitsviewer/opsfits.h" #ifdef HAVE_INDI #include "ekos/ekosmanager.h" #include "ekos/opsekos.h" #endif #endif #ifdef HAVE_XPLANET #include "xplanet/opsxplanet.h" #endif #ifdef HAVE_NOTIFYCONFIG #include #endif #include #include #include #include #include #include #include #ifdef _WIN32 #include #undef interface #endif #include #include "kstars_debug.h" /** ViewToolBar Action. All of the viewToolBar buttons are connected to this slot. **/ void KStars::slotViewToolBar() { KToggleAction *a = (KToggleAction *)sender(); KConfigDialog *kcd = KConfigDialog::exists("settings"); if (a == actionCollection()->action("show_stars")) { Options::setShowStars(a->isChecked()); if (kcd) { opcatalog->kcfg_ShowStars->setChecked(a->isChecked()); } } else if (a == actionCollection()->action("show_deepsky")) { Options::setShowDeepSky(a->isChecked()); if (kcd) { opcatalog->kcfg_ShowDeepSky->setChecked(a->isChecked()); } } else if (a == actionCollection()->action("show_planets")) { Options::setShowSolarSystem(a->isChecked()); if (kcd) { opsolsys->kcfg_ShowSolarSystem->setChecked(a->isChecked()); } } else if (a == actionCollection()->action("show_clines")) { Options::setShowCLines(a->isChecked()); if (kcd) { opguides->kcfg_ShowCLines->setChecked(a->isChecked()); } } else if (a == actionCollection()->action("show_cnames")) { Options::setShowCNames(a->isChecked()); if (kcd) { opguides->kcfg_ShowCNames->setChecked(a->isChecked()); } } else if (a == actionCollection()->action("show_cbounds")) { Options::setShowCBounds(a->isChecked()); if (kcd) { opguides->kcfg_ShowCBounds->setChecked(a->isChecked()); } } else if (a == actionCollection()->action("show_constellationart")) { Options::setShowConstellationArt(a->isChecked()); if (kcd) { opguides->kcfg_ShowConstellationArt->setChecked(a->isChecked()); } } else if (a == actionCollection()->action("show_mw")) { Options::setShowMilkyWay(a->isChecked()); if (kcd) { opguides->kcfg_ShowMilkyWay->setChecked(a->isChecked()); } } else if (a == actionCollection()->action("show_equatorial_grid")) { // if autoSelectGrid is selected and the user clicked the // show_equatorial_grid button, he probably wants us to disable // the autoSelectGrid and display the equatorial grid. Options::setAutoSelectGrid(false); Options::setShowEquatorialGrid(a->isChecked()); if (kcd) { opguides->kcfg_ShowEquatorialGrid->setChecked(a->isChecked()); opguides->kcfg_AutoSelectGrid->setChecked(false); } } else if (a == actionCollection()->action("show_horizontal_grid")) { Options::setAutoSelectGrid(false); Options::setShowHorizontalGrid(a->isChecked()); if (kcd) { opguides->kcfg_ShowHorizontalGrid->setChecked(a->isChecked()); opguides->kcfg_AutoSelectGrid->setChecked(false); } } else if (a == actionCollection()->action("show_horizon")) { Options::setShowGround(a->isChecked()); if (!a->isChecked() && Options::useRefraction()) { QString caption = i18n("Refraction effects disabled"); QString message = i18n("When the horizon is switched off, refraction effects are temporarily disabled."); KMessageBox::information(this, message, caption, "dag_refract_hide_ground"); } if (kcd) { opguides->kcfg_ShowGround->setChecked(a->isChecked()); } } else if (a == actionCollection()->action("show_flags")) { Options::setShowFlags(a->isChecked()); if (kcd) { opguides->kcfg_ShowFlags->setChecked(a->isChecked()); } } else if (a == actionCollection()->action("show_satellites")) { Options::setShowSatellites(a->isChecked()); if (kcd) { opssatellites->kcfg_ShowSatellites->setChecked(a->isChecked()); } } else if (a == actionCollection()->action("show_supernovae")) { Options::setShowSupernovae(a->isChecked()); if (kcd) { opssupernovae->kcfg_ShowSupernovae->setChecked(a->isChecked()); } } // update time for all objects because they might be not initialized // it's needed when using horizontal coordinates data()->setFullTimeUpdate(); updateTime(); map()->forceUpdate(); } void KStars::slotINDIToolBar() { #ifdef HAVE_INDI KToggleAction *a = (KToggleAction *)sender(); if (a == actionCollection()->action("show_control_panel")) { if (a->isChecked()) { GUIManager::Instance()->raise(); GUIManager::Instance()->activateWindow(); GUIManager::Instance()->showNormal(); } else GUIManager::Instance()->hide(); } else if (a == actionCollection()->action("show_ekos")) { if (a->isChecked()) { ekosManager()->raise(); ekosManager()->activateWindow(); ekosManager()->showNormal(); } else ekosManager()->hide(); } else if (a == actionCollection()->action("lock_telescope")) { if (INDIListener::Instance()->size() == 0) { KMessageBox::sorry(0, i18n("KStars did not find any active telescopes.")); return; } ISD::GDInterface *oneScope = nullptr; foreach (ISD::GDInterface *gd, INDIListener::Instance()->getDevices()) { INDI::BaseDevice *bd = gd->getBaseDevice(); if (gd->getType() != KSTARS_TELESCOPE) continue; if (bd == nullptr) continue; if (bd->isConnected() == false) { KMessageBox::error( 0, i18n("Telescope %1 is offline. Please connect and retry again.", gd->getDeviceName())); return; } oneScope = gd; break; } if (oneScope == nullptr) { KMessageBox::sorry(0, i18n("KStars did not find any active telescopes.")); return; } if (a->isChecked()) oneScope->runCommand(INDI_CENTER_LOCK); else oneScope->runCommand(INDI_CENTER_UNLOCK); } else if (a == actionCollection()->action("show_fits_viewer")) { if (m_FITSViewers.isEmpty()) { a->setEnabled(false); return; } if (a->isChecked()) { foreach (FITSViewer *view, m_FITSViewers) { if (view->getTabs().empty() == false) { view->raise(); view->activateWindow(); view->showNormal(); } } } else { foreach (FITSViewer *view, m_FITSViewers) { view->hide(); } } } #endif } void KStars::slotSetTelescopeEnabled(bool enable) { telescopeGroup->setEnabled(enable); if (enable == false) { for(QAction *a : telescopeGroup->actions()) { a->setChecked(false); } } } /** Major Dialog Window Actions **/ void KStars::slotCalculator() { if (!m_AstroCalc) m_AstroCalc = new AstroCalc(this); m_AstroCalc->show(); } void KStars::slotWizard() { QPointer wizard = new KSWizard(this); if (wizard->exec() == QDialog::Accepted) { Options::setRunStartupWizard(false); //don't run on startup next time updateLocationFromWizard(*(wizard->geo())); } delete wizard; } void KStars::updateLocationFromWizard(const GeoLocation& geo) { data()->setLocation(geo); // adjust local time to keep UT the same. // create new LT without DST offset KStarsDateTime ltime = data()->geo()->UTtoLT(data()->ut()); // reset timezonerule to compute next dst change data()->geo()->tzrule()->reset_with_ltime(ltime, data()->geo()->TZ0(), data()->isTimeRunningForward()); // reset next dst change time data()->setNextDSTChange(data()->geo()->tzrule()->nextDSTChange()); // reset local sideral time data()->syncLST(); // Make sure Numbers, Moon, planets, and sky objects are updated immediately data()->setFullTimeUpdate(); // If the sky is in Horizontal mode and not tracking, reset focus such that // Alt/Az remain constant. if (!Options::isTracking() && Options::useAltAz()) { map()->focus()->HorizontalToEquatorial(data()->lst(), data()->geo()->lat()); } // recalculate new times and objects data()->setSnapNextFocus(); updateTime(); } void KStars::slotDownload() { // 2017-07-04: Explicitly load kstars.knsrc from resources file QPointer dlg(new KNS3::DownloadDialog(":/kconfig/kstars.knsrc", this)); dlg->exec(); // Get the list of all the installed entries. KNS3::Entry::List installed_entries; KNS3::Entry::List changed_entries; if (dlg) { installed_entries = dlg->installedEntries(); changed_entries = dlg->changedEntries(); } delete dlg; foreach (const KNS3::Entry &entry, installed_entries) { foreach (const QString &name, entry.installedFiles()) { if (name.endsWith(QLatin1String(".cat"))) { data()->catalogdb()->AddCatalogContents(name); // To start displaying the custom catalog, add it to SkyMapComposite QString catalogName = data()->catalogdb()->GetCatalogName(name); Options::setShowCatalogNames(Options::showCatalogNames() << catalogName); Options::setCatalogFile(Options::catalogFile() << name); Options::setShowCatalog(Options::showCatalog() << 1); } } KStars::Instance()->data()->skyComposite()->reloadDeepSky(); // update time for all objects because they might be not initialized // it's needed when using horizontal coordinates KStars::Instance()->data()->setFullTimeUpdate(); KStars::Instance()->updateTime(); KStars::Instance()->map()->forceUpdate(); } foreach (const KNS3::Entry &entry, changed_entries) { foreach (const QString &name, entry.uninstalledFiles()) { if (name.endsWith(QLatin1String(".cat"))) { data()->catalogdb()->RemoveCatalog(name); // To start displaying the custom catalog, add it to SkyMapComposite QStringList catFile = Options::catalogFile(); catFile.removeOne(name); Options::setCatalogFile(catFile); } } } } void KStars::slotAVT() { if (!m_AltVsTime) m_AltVsTime = new AltVsTime(this); m_AltVsTime->show(); } void KStars::slotWUT() { if (!m_WUTDialog) m_WUTDialog = new WUTDialog(this); m_WUTDialog->show(); } //FIXME Port to QML2 //#if 0 void KStars::slotWISettings() { if (!m_WIView) slotToggleWIView(); if (m_WIView && !m_wiDock->isVisible()) slotToggleWIView(); if (KConfigDialog::showDialog("wisettings")) { m_WIEquipmentSettings->populateScopeListWidget(); return; } KConfigDialog *dialog = new KConfigDialog(this, "wisettings", Options::self()); connect(dialog, SIGNAL(settingsChanged(QString)), this, SLOT(slotApplyWIConfigChanges())); m_WISettings = new WILPSettings(this); m_WIEquipmentSettings = new WIEquipSettings(); dialog->addPage(m_WISettings, i18n("Light Pollution Settings")); dialog->addPage(m_WIEquipmentSettings, i18n("Equipment Settings - Equipment Type and Parameters")); dialog->exec(); if (m_WIEquipmentSettings) m_WIEquipmentSettings->setAperture(); //Something isn't working with this! } void KStars::slotToggleWIView() { if (KStars::Closing) return; if (!m_WIView) { - m_WIView = new WIView(0); + m_WIView = new WIView(nullptr); m_wiDock = new QDockWidget(this); m_wiDock->setStyleSheet("QDockWidget::title{background-color:black;}"); m_wiDock->setObjectName("What's Interesting"); m_wiDock->setAllowedAreas(Qt::RightDockWidgetArea); QWidget *container = QWidget::createWindowContainer(m_WIView->getWIBaseView()); m_wiDock->setWidget(container); m_wiDock->setMinimumWidth(400); addDockWidget(Qt::RightDockWidgetArea, m_wiDock); connect(m_wiDock, SIGNAL(visibilityChanged(bool)), actionCollection()->action("show_whatsinteresting"), SLOT(setChecked(bool))); m_wiDock->setVisible(true); } else { m_wiDock->setVisible(!m_wiDock->isVisible()); } } void KStars::slotCalendar() { if (!m_SkyCalendar) m_SkyCalendar = new SkyCalendar(this); m_SkyCalendar->show(); } void KStars::slotGlossary() { // GlossaryDialog *dlg = new GlossaryDialog( this, true ); // QString glossaryfile =data()->stdDirs->findResource( "data", "kstars/glossary.xml" ); // QUrl u = glossaryfile; // Glossary *g = new Glossary( u ); // g->setName( i18n( "Knowledge" ) ); // dlg->addGlossary( g ); // dlg->show(); } void KStars::slotScriptBuilder() { if (!m_ScriptBuilder) m_ScriptBuilder = new ScriptBuilder(this); m_ScriptBuilder->show(); } void KStars::slotSolarSystem() { if (!m_PlanetViewer) m_PlanetViewer = new PlanetViewer(this); m_PlanetViewer->show(); } /* void KStars::slotJMoonTool() { if ( ! m_JMoonTool ) m_JMoonTool = new JMoonTool(this); m_JMoonTool->show(); } */ void KStars::slotMoonPhaseTool() { //FIXME Port to KF5 //if( ! mpt ) mpt = new MoonPhaseTool( this ); //mpt->show(); } void KStars::slotFlagManager() { if (!m_FlagManager) m_FlagManager = new FlagManager(this); m_FlagManager->show(); } void KStars::slotTelescopeWizard() { #ifdef HAVE_INDI #ifndef Q_OS_WIN QString indiServerDir = Options::indiServer(); #ifdef Q_OS_OSX if (Options::indiServerIsInternal()) indiServerDir = QCoreApplication::applicationDirPath() + "/indi"; else indiServerDir = QFileInfo(Options::indiServer()).dir().path(); #endif QStringList paths; paths << "/usr/bin" << "/usr/local/bin" << indiServerDir; if (QStandardPaths::findExecutable("indiserver").isEmpty()) { if (QStandardPaths::findExecutable("indiserver", paths).isEmpty()) { KMessageBox::error(nullptr, i18n("Unable to find INDI server. Please make sure the package that provides " "the 'indiserver' binary is installed.")); return; } } #endif QPointer twiz = new telescopeWizardProcess(this); twiz->exec(); delete twiz; #endif } void KStars::slotINDIPanel() { #ifdef HAVE_INDI #ifndef Q_OS_WIN QString indiServerDir = Options::indiServer(); #ifdef Q_OS_OSX if (Options::indiServerIsInternal()) indiServerDir = QCoreApplication::applicationDirPath() + "/indi"; else indiServerDir = QFileInfo(Options::indiServer()).dir().path(); #endif QStringList paths; paths << "/usr/bin" << "/usr/local/bin" << indiServerDir; if (QStandardPaths::findExecutable("indiserver").isEmpty()) { if (QStandardPaths::findExecutable("indiserver", paths).isEmpty()) { KMessageBox::error(nullptr, i18n("Unable to find INDI server. Please make sure the package that provides " "the 'indiserver' binary is installed.")); return; } } #endif GUIManager::Instance()->updateStatus(true); #endif } void KStars::slotINDIDriver() { #ifdef HAVE_INDI #ifndef Q_OS_WIN QString indiServerDir = Options::indiServer(); #ifdef Q_OS_OSX if (Options::indiServerIsInternal()) indiServerDir = QCoreApplication::applicationDirPath() + "/indi"; else indiServerDir = QFileInfo(Options::indiServer()).dir().path(); #endif QStringList paths; paths << "/usr/bin" << "/usr/local/bin" << indiServerDir; if (QStandardPaths::findExecutable("indiserver").isEmpty()) { if (QStandardPaths::findExecutable("indiserver", paths).isEmpty()) { KMessageBox::error(nullptr, i18n("Unable to find INDI server. Please make sure the package that provides " "the 'indiserver' binary is installed.")); return; } } #endif DriverManager::Instance()->raise(); DriverManager::Instance()->activateWindow(); DriverManager::Instance()->showNormal(); #endif } void KStars::slotEkos() { #ifdef HAVE_CFITSIO #ifdef HAVE_INDI #ifndef Q_OS_WIN QString indiServerDir = Options::indiServer(); #ifdef Q_OS_OSX if (Options::indiServerIsInternal()) indiServerDir = QCoreApplication::applicationDirPath() + "/indi"; else indiServerDir = QFileInfo(Options::indiServer()).dir().path(); #endif QStringList paths; paths << "/usr/bin" << "/usr/local/bin" << indiServerDir; if (QStandardPaths::findExecutable("indiserver").isEmpty()) { if (QStandardPaths::findExecutable("indiserver", paths).isEmpty()) { KMessageBox::error(nullptr, i18n("Unable to find INDI server. Please make sure the package that provides " "the 'indiserver' binary is installed.")); return; } } #endif if (ekosManager()->isVisible() && ekosManager()->isActiveWindow()) { ekosManager()->hide(); } else { ekosManager()->raise(); ekosManager()->activateWindow(); ekosManager()->showNormal(); } #endif #endif } void KStars::slotINDITelescopeTrack() { #ifdef HAVE_INDI if (m_KStarsData == nullptr || INDIListener::Instance() == nullptr) return; for (auto *gd : INDIListener::Instance()->getDevices()) { ISD::Telescope* telescope = dynamic_cast(gd); if (telescope != nullptr && telescope->isConnected()) { KToggleAction *a = qobject_cast(sender()); if (a != nullptr) { telescope->setTrackEnabled(a->isChecked()); return; } } } #endif } void KStars::slotINDITelescopeSlew(bool focused_object) { #ifdef HAVE_INDI if (m_KStarsData == nullptr || INDIListener::Instance() == nullptr) return; for (auto *gd : INDIListener::Instance()->getDevices()) { ISD::Telescope* telescope = dynamic_cast(gd); if (telescope != nullptr && telescope->isConnected()) { if (focused_object) { if (m_SkyMap->focusObject() != nullptr) telescope->Slew(m_SkyMap->focusObject()); } else telescope->Slew(m_SkyMap->mousePoint()); return; } } #endif } void KStars::slotINDITelescopeSlewMousePointer() { #ifdef HAVE_INDI slotINDITelescopeSlew(false); #endif } void KStars::slotINDITelescopeSync(bool focused_object) { #ifdef HAVE_INDI if (m_KStarsData == nullptr || INDIListener::Instance() == nullptr) return; for (auto *gd : INDIListener::Instance()->getDevices()) { ISD::Telescope* telescope = dynamic_cast(gd); if (telescope != nullptr && telescope->isConnected() && telescope->canSync()) { if (focused_object) { if (m_SkyMap->focusObject() != nullptr) telescope->Sync(m_SkyMap->focusObject()); } else telescope->Sync(m_SkyMap->mousePoint()); return; } } #endif } void KStars::slotINDITelescopeSyncMousePointer() { #ifdef HAVE_INDI slotINDITelescopeSync(false); #endif } void KStars::slotINDITelescopeAbort() { #ifdef HAVE_INDI if (m_KStarsData == nullptr || INDIListener::Instance() == nullptr) return; for (auto *gd : INDIListener::Instance()->getDevices()) { ISD::Telescope* telescope = dynamic_cast(gd); if (telescope != nullptr && telescope->isConnected()) { telescope->Abort(); return; } } #endif } void KStars::slotINDITelescopePark() { #ifdef HAVE_INDI if (m_KStarsData == nullptr || INDIListener::Instance() == nullptr) return; for (auto *gd : INDIListener::Instance()->getDevices()) { ISD::Telescope* telescope = dynamic_cast(gd); if (telescope != nullptr && telescope->isConnected() && telescope->canPark()) { telescope->Park(); return; } } #endif } void KStars::slotINDITelescopeUnpark() { #ifdef HAVE_INDI if (m_KStarsData == nullptr || INDIListener::Instance() == nullptr) return; for (auto *gd : INDIListener::Instance()->getDevices()) { ISD::Telescope* telescope = dynamic_cast(gd); if (telescope != nullptr && telescope->isConnected() && telescope->canPark()) { telescope->UnPark(); return; } } #endif } void KStars::slotGeoLocator() { QPointer locationdialog = new LocationDialog(this); if (locationdialog->exec() == QDialog::Accepted) { GeoLocation *newLocation = locationdialog->selectedCity(); if (newLocation) { // set new location in options data()->setLocation(*newLocation); // adjust local time to keep UT the same. // create new LT without DST offset KStarsDateTime ltime = newLocation->UTtoLT(data()->ut()); // reset timezonerule to compute next dst change newLocation->tzrule()->reset_with_ltime(ltime, newLocation->TZ0(), data()->isTimeRunningForward()); // reset next dst change time data()->setNextDSTChange(newLocation->tzrule()->nextDSTChange()); // reset local sideral time data()->syncLST(); // Make sure Numbers, Moon, planets, and sky objects are updated immediately data()->setFullTimeUpdate(); // If the sky is in Horizontal mode and not tracking, reset focus such that // Alt/Az remain constant. if (!Options::isTracking() && Options::useAltAz()) { map()->focus()->HorizontalToEquatorial(data()->lst(), data()->geo()->lat()); } // recalculate new times and objects data()->setSnapNextFocus(); updateTime(); } } delete locationdialog; } void KStars::slotViewOps() { //An instance of your dialog could be already created and could be cached, //in which case you want to display the cached dialog instead of creating //another one if (KConfigDialog::showDialog("settings")) return; //KConfigDialog didn't find an instance of this dialog, so lets create it : KConfigDialog *dialog = new KConfigDialog(this, "settings", Options::self()); // For some reason the dialog does not resize to contents // so we set initial 'resonable' size here. Any better way to do this? dialog->resize(800,600); #ifdef Q_OS_OSX dialog->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif connect(dialog, SIGNAL(settingsChanged(QString)), this, SLOT(slotApplyConfigChanges())); opcatalog = new OpsCatalog(); opguides = new OpsGuides(); opsolsys = new OpsSolarSystem(); opssatellites = new OpsSatellites(); opssupernovae = new OpsSupernovae(); opcolors = new OpsColors(); opadvanced = new OpsAdvanced(); KPageWidgetItem *page; page = dialog->addPage(opcatalog, i18n("Catalogs"), "kstars_catalog"); page->setIcon(QIcon::fromTheme("kstars_catalog", QIcon(":/icons/breeze/default/kstars_catalog.svg"))); page = dialog->addPage(opsolsys, i18n("Solar System"), "kstars_solarsystem"); page->setIcon(QIcon::fromTheme("kstars_solarsystem", QIcon(":/icons/breeze/default/kstars_solarsystem.svg"))); page = dialog->addPage(opssatellites, i18n("Satellites"), "kstars_satellites"); page->setIcon(QIcon::fromTheme("kstars_satellites", QIcon(":/icons/breeze/default/kstars_satellites.svg"))); page = dialog->addPage(opssupernovae, i18n("Supernovae"), "kstars_supernovae"); page->setIcon(QIcon::fromTheme("kstars_supernovae", QIcon(":/icons/breeze/default/kstars_supernovae.svg"))); page = dialog->addPage(opguides, i18n("Guides"), "kstars_guides"); page->setIcon(QIcon::fromTheme("kstars_guides", QIcon(":/icons/breeze/default/kstars_guides.svg"))); page = dialog->addPage(opcolors, i18n("Colors"), "kstars_colors"); page->setIcon(QIcon::fromTheme("kstars_colors", QIcon(":/icons/breeze/default/kstars_colors.svg"))); #ifdef HAVE_CFITSIO opsfits = new OpsFITS(); page = dialog->addPage(opsfits, i18n("FITS"), "kstars_fitsviewer"); page->setIcon(QIcon::fromTheme("kstars_fitsviewer", QIcon(":/icons/breeze/default/kstars_fitsviewer.svg"))); #endif #ifdef HAVE_INDI opsindi = new OpsINDI(); page = dialog->addPage(opsindi, i18n("INDI"), "kstars_indi"); page->setIcon(QIcon::fromTheme("kstars_indi", QIcon(":/icons/breeze/default/kstars_indi.svg"))); #ifdef HAVE_CFITSIO opsekos = new OpsEkos(); KPageWidgetItem *ekosOption = dialog->addPage(opsekos, i18n("Ekos"), "kstars_ekos"); ekosOption->setIcon(QIcon::fromTheme("kstars_ekos", QIcon(":/icons/breeze/default/kstars_ekos.svg"))); if (m_EkosManager) m_EkosManager->setOptionsWidget(ekosOption); #endif #endif #ifdef HAVE_XPLANET opsxplanet = new OpsXplanet(this); page = dialog->addPage(opsxplanet, i18n("Xplanet"), "kstars_xplanet"); page->setIcon(QIcon::fromTheme("kstars_xplanet", QIcon(":/icons/breeze/default/kstars_xplanet.svg"))); #endif page = dialog->addPage(opadvanced, i18n("Advanced"), "kstars_advanced"); page->setIcon(QIcon::fromTheme("kstars_advanced", QIcon(":/icons/breeze/default/kstars_advanced.svg"))); dialog->show(); } void KStars::slotApplyConfigChanges() { Options::self()->save(); applyConfig(); //data()->setFullTimeUpdate(); //map()->forceUpdate(); } void KStars::slotApplyWIConfigChanges() { Options::self()->save(); applyConfig(); m_WIView->updateObservingConditions(); m_WIView->onReloadIconClicked(); } void KStars::slotSetTime() { QPointer timedialog = new TimeDialog(data()->lt(), data()->geo(), this); if (timedialog->exec() == QDialog::Accepted) { data()->changeDateTime(data()->geo()->LTtoUT(timedialog->selectedDateTime())); if (Options::useAltAz()) { if (map()->focusObject()) { map()->focusObject()->EquatorialToHorizontal(data()->lst(), data()->geo()->lat()); map()->setFocus(map()->focusObject()); } else map()->focus()->HorizontalToEquatorial(data()->lst(), data()->geo()->lat()); } map()->forceUpdateNow(); //If focusObject has a Planet Trail, clear it and start anew. KSPlanetBase *planet = dynamic_cast(map()->focusObject()); if (planet && planet->hasTrail()) { planet->clearTrail(); planet->addToTrail(); } } delete timedialog; } //Set Time to CPU clock void KStars::slotSetTimeToNow() { data()->changeDateTime(KStarsDateTime::currentDateTimeUtc()); if (Options::useAltAz()) { if (map()->focusObject()) { map()->focusObject()->EquatorialToHorizontal(data()->lst(), data()->geo()->lat()); map()->setFocus(map()->focusObject()); } else map()->focus()->HorizontalToEquatorial(data()->lst(), data()->geo()->lat()); } map()->forceUpdateNow(); //If focusObject has a Planet Trail, clear it and start anew. KSPlanetBase *planet = dynamic_cast(map()->focusObject()); if (planet && planet->hasTrail()) { planet->clearTrail(); planet->addToTrail(); } } void KStars::slotFind() { clearCachedFindDialog(); if (!m_FindDialog) // create new dialog if no dialog is existing { m_FindDialog = new FindDialog(this); } if (!m_FindDialog) qWarning() << i18n("KStars::slotFind() - Not enough memory for dialog"); SkyObject *targetObject; if (m_FindDialog->exec() == QDialog::Accepted && (targetObject = m_FindDialog->targetObject())) { map()->setClickedObject(targetObject); map()->setClickedPoint(map()->clickedObject()); map()->slotCenter(); } // check if data has changed while dialog was open if (DialogIsObsolete) clearCachedFindDialog(); } void KStars::slotOpenFITS() { #ifdef HAVE_CFITSIO static QUrl path = QUrl::fromLocalFile(QDir::homePath()); QUrl fileURL = QFileDialog::getOpenFileUrl(KStars::Instance(), i18n("Open FITS"), path, "FITS (*.fits *.fit *.fts)"); if (fileURL.isEmpty()) return; // Remember last directory path.setUrl(fileURL.url(QUrl::RemoveFilename)); FITSViewer *fv = new FITSViewer((Options::independentWindowFITS()) ? nullptr : this); // Error opening file if (fv->addFITS(&fileURL, FITS_NORMAL, FITS_NONE, QString(), false) == -2) delete (fv); else fv->show(); #endif } void KStars::slotExportImage() { //TODO Check this //For remote files, this returns //QFileInfo::absolutePath: QFileInfo::absolutePath: Constructed with empty filename //As of 2014-07-19 //QUrl fileURL = KFileDialog::getSaveUrl( QDir::homePath(), "image/png image/jpeg image/gif image/x-portable-pixmap image/bmp image/svg+xml" ); QUrl fileURL = QFileDialog::getSaveFileUrl(KStars::Instance(), i18n("Export Image"), QUrl(), "Images (*.png *.jpeg *.gif *.bmp *.svg)"); //User cancelled file selection dialog - abort image export if (fileURL.isEmpty()) { return; } //Warn user if file exists! if (QFile::exists(fileURL.toLocalFile())) { int r = KMessageBox::warningContinueCancel( parentWidget(), i18n("A file named \"%1\" already exists. Overwrite it?", fileURL.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite()); if (r == KMessageBox::Cancel) return; } // execute image export dialog // Note: We don't let ExportImageDialog create its own ImageExporter because we want legend settings etc to be remembered between UI use and DBus scripting interface use. //if ( !m_ImageExporter ) //m_ImageExporter = new ImageExporter( this ); if (!m_ExportImageDialog) { m_ExportImageDialog = new ExportImageDialog(fileURL.toLocalFile(), QSize(map()->width(), map()->height()), KStarsData::Instance()->imageExporter()); } else { m_ExportImageDialog->setOutputUrl(fileURL.toLocalFile()); m_ExportImageDialog->setOutputSize(QSize(map()->width(), map()->height())); } m_ExportImageDialog->show(); } void KStars::slotRunScript() { QUrl fileURL = QFileDialog::getOpenFileUrl( KStars::Instance(), QString(), QUrl(QDir::homePath()), "*.kstars|" + i18nc("Filter by file type: KStars Scripts.", "KStars Scripts (*.kstars)")); QFile f; //QString fname; if (fileURL.isValid()) { if (fileURL.isLocalFile() == false) { - KMessageBox::sorry(0, i18n("Executing remote scripts is not supported.")); + KMessageBox::sorry(nullptr, i18n("Executing remote scripts is not supported.")); return; } /* if ( ! fileURL.isLocalFile() ) { //Warn the user about executing remote code. QString message = i18n( "Warning: You are about to execute a remote shell script on your machine. " ); message += i18n( "If you absolutely trust the source of this script, press Continue to execute the script; " ); message += i18n( "to save the file without executing it, press Save; " ); message += i18n( "to cancel the download, press Cancel. " ); int result = KMessageBox::warningYesNoCancel( 0, message, i18n( "Really Execute Remote Script?" ), KStandardGuiItem::cont(), KStandardGuiItem::save() ); if ( result == KMessageBox::Cancel ) return; if ( result == KMessageBox::No ) { //save file QUrl saveURL = QFileDialog::getSaveFileUrl(KStars::Instance(), QString(), QUrl(QDir::homePath()), "*.kstars|" + i18nc("Filter by file type: KStars Scripts.", "KStars Scripts (*.kstars)") ); QTemporaryFile tmpfile; tmpfile.open(); while ( ! saveURL.isValid() ) { message = i18n( "Save location is invalid. Try another location?" ); if ( KMessageBox::warningYesNo( 0, message, i18n( "Invalid Save Location" ), KGuiItem(i18n("Try Another")), KGuiItem(i18n("Do Not Try")) ) == KMessageBox::No ) return; saveURL = QFileDialog::getSaveFileUrl(KStars::Instance(), QString(), QUrl(QDir::homePath()), "*.kstars|" + i18nc("Filter by file type: KStars Scripts.", "KStars Scripts (*.kstars)") ); } if ( saveURL.isLocalFile() ) { fname = saveURL.toLocalFile(); } else { fname = tmpfile.fileName(); } //if( KIO::NetAccess::download( fileURL, fname, this ) ) { if (KIO::file_copy(fileURL, QUrl(fname))->exec() == true) { #ifndef _WIN32 chmod( fname.toLatin1(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH ); //make it executable #endif if ( tmpfile.fileName() == fname ) { //upload to remote location //if ( ! KIO::NetAccess::upload( tmpfile.fileName(), fileURL, this ) ) QUrl sourceURL(tmpfile.fileName()); sourceURL.setScheme("file"); if (KIO::file_copy(sourceURL, fileURL)->exec() == false) { QString message = i18n( "Could not upload image to remote location: %1", fileURL.url() ); KMessageBox::sorry( 0, message, i18n( "Could not upload file" ) ); } } } else { KMessageBox::sorry( 0, i18n( "Could not download the file." ), i18n( "Download Error" ) ); } return; } } */ //Damn the torpedos and full speed ahead, we're executing the script! QTemporaryFile tmpfile; tmpfile.open(); /* if ( ! fileURL.isLocalFile() ) { fname = tmpfile.fileName(); //if( KIO::NetAccess::download( fileURL, fname, this ) ) if (KIO::file_copy(fileURL, QUrl(fname))->exec() == true) { #ifndef _WIN32 chmod( fname.toLatin1(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH ); #endif f.setFileName( fname ); } } else { f.setFileName( fileURL.toLocalFile() ); }*/ f.setFileName(fileURL.toLocalFile()); if (!f.open(QIODevice::ReadOnly)) { QString message = i18n("Could not open file %1", f.fileName()); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); return; } // Before we run the script, make sure that it's safe. Each line must either begin with "#" // or begin with "dbus-send". INDI scripts are much more complicated, so this simple test is not // suitable. INDI Scripting will return in KDE 4.1 QTextStream istream(&f); QString line; bool fileOK(true); while (!istream.atEnd()) { line = istream.readLine(); if (line.at(0) != '#' && line.left(9) != "dbus-send") { fileOK = false; break; } } if (!fileOK) { int answer; answer = KMessageBox::warningContinueCancel( - 0, + nullptr, i18n("The selected script contains unrecognized elements, " "indicating that it was not created using the KStars script builder. " "This script may not function properly, and it may even contain malicious code. " "Would you like to execute it anyway?"), i18n("Script Validation Failed"), KGuiItem(i18n("Run Nevertheless")), KStandardGuiItem::cancel(), "daExecuteScript"); if (answer == KMessageBox::Cancel) return; } //Add statusbar message that script is running statusBar()->showMessage(i18n("Running script: %1", fileURL.fileName())); QProcess p; p.start(f.fileName()); if (!p.waitForStarted()) return; while (!p.waitForFinished(10)) { qApp->processEvents(); //otherwise tempfile may get deleted before script completes. if (p.state() != QProcess::Running) break; } statusBar()->showMessage(i18n("Script finished."), 0); } } void KStars::slotPrint() { bool switchColors(false); //Suggest Chart color scheme if (data()->colorScheme()->colorNamed("SkyColor") != QColor(255, 255, 255)) { QString message = i18n("You can save printer ink by using the \"Star Chart\" " "color scheme, which uses a white background. Would you like to " "temporarily switch to the Star Chart color scheme for printing?"); int answer; answer = KMessageBox::questionYesNoCancel( - 0, message, i18n("Switch to Star Chart Colors?"), KGuiItem(i18n("Switch Color Scheme")), + nullptr, message, i18n("Switch to Star Chart Colors?"), KGuiItem(i18n("Switch Color Scheme")), KGuiItem(i18n("Do Not Switch")), KStandardGuiItem::cancel(), "askAgainPrintColors"); if (answer == KMessageBox::Cancel) return; if (answer == KMessageBox::Yes) switchColors = true; } printImage(true, switchColors); } void KStars::slotPrintingWizard() { if (m_PrintingWizard) { delete m_PrintingWizard; } m_PrintingWizard = new PrintingWizard(this); m_PrintingWizard->show(); } void KStars::slotToggleTimer() { if (data()->clock()->isActive()) { data()->clock()->stop(); updateTime(); } else { if (fabs(data()->clock()->scale()) > Options::slewTimeScale()) data()->clock()->setManualMode(true); data()->clock()->start(); if (data()->clock()->isManualMode()) map()->forceUpdate(); } // Update clock state in options Options::setRunClock(data()->clock()->isActive()); } void KStars::slotStepForward() { if (data()->clock()->isActive()) data()->clock()->stop(); data()->clock()->manualTick(true); map()->forceUpdate(); } void KStars::slotStepBackward() { if (data()->clock()->isActive()) data()->clock()->stop(); data()->clock()->manualTick(true, true); map()->forceUpdate(); } //Pointing void KStars::slotPointFocus() { // In the following cases, we set slewing=true in order to disengage tracking map()->stopTracking(); if (sender() == actionCollection()->action("zenith")) map()->setDestinationAltAz(dms(90.0), map()->focus()->az()); else if (sender() == actionCollection()->action("north")) map()->setDestinationAltAz(dms(15.0), dms(0.0001)); else if (sender() == actionCollection()->action("east")) map()->setDestinationAltAz(dms(15.0), dms(90.0)); else if (sender() == actionCollection()->action("south")) map()->setDestinationAltAz(dms(15.0), dms(180.0)); else if (sender() == actionCollection()->action("west")) map()->setDestinationAltAz(dms(15.0), dms(270.0)); } void KStars::slotTrack() { if (Options::isTracking()) { Options::setIsTracking(false); actionCollection()->action("track_object")->setText(i18n("Engage &Tracking")); actionCollection() ->action("track_object") ->setIcon(QIcon::fromTheme("document-decrypt", QIcon(":/icons/breeze/default/document-encrypt.svg"))); KSPlanetBase *planet = dynamic_cast(map()->focusObject()); if (planet && data()->temporaryTrail) { planet->clearTrail(); data()->temporaryTrail = false; } map()->setClickedObject(nullptr); map()->setFocusObject(nullptr); //no longer tracking focusObject map()->setFocusPoint(nullptr); } else { map()->setClickedPoint(map()->focus()); map()->setClickedObject(nullptr); map()->setFocusObject(nullptr); //no longer tracking focusObject map()->setFocusPoint(map()->clickedPoint()); Options::setIsTracking(true); actionCollection()->action("track_object")->setText(i18n("Stop &Tracking")); actionCollection() ->action("track_object") ->setIcon(QIcon::fromTheme("document-encrypt", QIcon(":/icons/breeze/default/document-encrypt.svg"))); } map()->forceUpdate(); } void KStars::slotManualFocus() { QPointer focusDialog = new FocusDialog(); if (Options::useAltAz()) focusDialog->activateAzAltPage(); if (focusDialog->exec() == QDialog::Accepted) { //If the requested position is very near the pole, we need to point first //to an intermediate location just below the pole in order to get the longitudinal //position (RA/Az) right. double realAlt(focusDialog->point()->alt().Degrees()); double realDec(focusDialog->point()->dec().Degrees()); if (Options::useAltAz() && realAlt > 89.0) { focusDialog->point()->setAlt(89.0); focusDialog->point()->HorizontalToEquatorial(data()->lst(), data()->geo()->lat()); } if (!Options::useAltAz() && realDec > 89.0) { focusDialog->point()->setDec(89.0); focusDialog->point()->EquatorialToHorizontal(data()->lst(), data()->geo()->lat()); } map()->setClickedPoint(focusDialog->point()); if (Options::isTracking()) slotTrack(); map()->slotCenter(); //The slew takes some time to complete, and this often causes the final focus point to be slightly //offset from the user's requested coordinates (because EquatorialToHorizontal() is called //throughout the process, which depends on the sidereal time). So we now "polish" the final //position by resetting the final focus to the focusDialog point. // //Also, if the requested position was within 1 degree of the coordinate pole, this will //automatically correct the final pointing from the intermediate offset position to the final position data()->setSnapNextFocus(); if (Options::useAltAz()) { map()->setDestinationAltAz(focusDialog->point()->alt(), focusDialog->point()->az()); } else { map()->setDestination(focusDialog->point()->ra(), focusDialog->point()->dec()); } //Now, if the requested point was near a pole, we need to reset the Alt/Dec of the focus. if (Options::useAltAz() && realAlt > 89.0) map()->focus()->setAlt(realAlt); if (!Options::useAltAz() && realDec > 89.0) map()->focus()->setDec(realAlt); //Don't track if we set Alt/Az coordinates. This way, Alt/Az remain constant. if (focusDialog->usedAltAz()) map()->stopTracking(); } delete focusDialog; } void KStars::slotZoomChanged() { // Enable/disable actions actionCollection()->action("zoom_out")->setEnabled(Options::zoomFactor() > MINZOOM); actionCollection()->action("zoom_in")->setEnabled(Options::zoomFactor() < MAXZOOM); // Update status bar map()->setupProjector(); // this needs to be run redundantly, so that the FOV returned below is up-to-date. float fov = map()->projector()->fov(); KLocalizedString fovi18nstring = ki18nc("approximate field of view", "Approximate FOV: %1 degrees"); if (fov < 1.0) { fov = fov * 60.0; fovi18nstring = ki18nc("approximate field of view", "Approximate FOV: %1 arcminutes"); } if (fov < 1.0) { fov = fov * 60.0; fovi18nstring = ki18nc("approximate field of view", "Approximate FOV: %1 arcseconds"); } QString fovstring = fovi18nstring.subs(QString::number(fov, 'f', 1)).toString(); statusBar()->showMessage(fovstring, 0); } void KStars::slotSetZoom() { bool ok; double currentAngle = map()->width() / (Options::zoomFactor() * dms::DegToRad); double minAngle = map()->width() / (MAXZOOM * dms::DegToRad); double maxAngle = map()->width() / (MINZOOM * dms::DegToRad); double angSize = QInputDialog::getDouble( - 0, + nullptr, i18nc("The user should enter an angle for the field-of-view of the display", "Enter Desired Field-of-View Angle"), i18n("Enter a field-of-view angle in degrees: "), currentAngle, minAngle, maxAngle, 1, &ok); if (ok) { map()->setZoomFactor(map()->width() / (angSize * dms::DegToRad)); } } void KStars::slotCoordSys() { if (Options::useAltAz()) { Options::setUseAltAz(false); if (Options::useRefraction()) { if (map()->focusObject()) //simply update focus to focusObject's position map()->setFocus(map()->focusObject()); else //need to recompute focus for unrefracted position { map()->setFocusAltAz(SkyPoint::unrefract(map()->focus()->alt()), map()->focus()->az()); map()->focus()->HorizontalToEquatorial(data()->lst(), data()->geo()->lat()); } } actionCollection()->action("coordsys")->setText(i18n("Switch to horizonal view (Horizontal &Coordinates)")); } else { Options::setUseAltAz(true); if (Options::useRefraction()) { map()->setFocusAltAz(map()->focus()->altRefracted(), map()->focus()->az()); } actionCollection()->action("coordsys")->setText(i18n("Switch to star globe view (Equatorial &Coordinates)")); } map()->forceUpdate(); } void KStars::slotMapProjection() { if (sender() == actionCollection()->action("project_lambert")) Options::setProjection(Projector::Lambert); if (sender() == actionCollection()->action("project_azequidistant")) Options::setProjection(Projector::AzimuthalEquidistant); if (sender() == actionCollection()->action("project_orthographic")) Options::setProjection(Projector::Orthographic); if (sender() == actionCollection()->action("project_equirectangular")) Options::setProjection(Projector::Equirectangular); if (sender() == actionCollection()->action("project_stereographic")) Options::setProjection(Projector::Stereographic); if (sender() == actionCollection()->action("project_gnomonic")) Options::setProjection(Projector::Gnomonic); //DEBUG qCDebug(KSTARS) << "Projection system: " << Options::projection(); m_SkyMap->forceUpdate(); } //Settings Menu: void KStars::slotColorScheme() { //use mid(3) to exclude the leading "cs_" prefix from the action name QString filename = QString(sender()->objectName()).mid(3) + ".colors"; loadColorScheme(filename); } void KStars::slotTargetSymbol(bool flag) { qDebug() << QString("slotTargetSymbol: %1 %2").arg(sender()->objectName()).arg(flag); QStringList names = Options::fOVNames(); if (flag) { // Add FOV to list names.append(sender()->objectName()); } else { // Remove FOV from list int ix = names.indexOf(sender()->objectName()); if (ix >= 0) names.removeAt(ix); } Options::setFOVNames(names); // Sync visibleFOVs with fovNames data()->syncFOV(); map()->forceUpdate(); } void KStars::slotHIPSSource() { QAction *selectedAction = qobject_cast(sender()); Q_ASSERT(selectedAction != nullptr); HIPSManager::Instance()->setCurrentSource(selectedAction->text().remove("&")); map()->forceUpdate(); } void KStars::slotFOVEdit() { QPointer fovdlg = new FOVDialog(this); if (fovdlg->exec() == QDialog::Accepted) { FOVManager::save(); repopulateFOV(); } delete fovdlg; } void KStars::slotObsList() { m_KStarsData->observingList()->show(); } void KStars::slotEquipmentWriter() { QPointer equipmentdlg = new EquipmentWriter(); equipmentdlg->loadEquipment(); equipmentdlg->exec(); delete equipmentdlg; } void KStars::slotObserverManager() { QPointer m_observerAdd = new ObserverAdd(); m_observerAdd->exec(); delete m_observerAdd; } void KStars::slotHorizonManager() { if (!m_HorizonManager) { m_HorizonManager = new HorizonManager(this); connect(m_SkyMap, SIGNAL(positionClicked(SkyPoint*)), m_HorizonManager, SLOT(addSkyPoint(SkyPoint*))); } m_HorizonManager->show(); } void KStars::slotEyepieceView(SkyPoint *sp, const QString &imagePath) { if (!m_EyepieceView) m_EyepieceView = new EyepieceField(this); // FIXME: Move FOV choice into the Eyepiece View tool itself. bool ok = true; - const FOV *fov = 0; + const FOV *fov = nullptr; if (!data()->getAvailableFOVs().isEmpty()) { // Ask the user to choose from a list of available FOVs. //int index; const FOV *f; QMap nameToFovMap; foreach (f, data()->getAvailableFOVs()) { nameToFovMap.insert(f->name(), f); } nameToFovMap.insert(i18n("Attempt to determine from image"), 0); fov = nameToFovMap[QInputDialog::getItem(this, i18n("Eyepiece View: Choose a field-of-view"), i18n("FOV to render eyepiece view for:"), nameToFovMap.uniqueKeys(), 0, false, &ok)]; } if (ok) m_EyepieceView->showEyepieceField(sp, fov, imagePath); } void KStars::slotExecute() { KStarsData::Instance()->executeSession()->init(); KStarsData::Instance()->executeSession()->show(); } void KStars::slotPolarisHourAngle() { QPointer pHourAngle = new PolarisHourAngle(this); pHourAngle->exec(); } //Help Menu void KStars::slotTipOfDay() { KTipDialog::showTip(this, "kstars/tips", true); } // Toggle to and from full screen mode void KStars::slotFullScreen() { if (topLevelWidget()->isFullScreen()) { topLevelWidget()->setWindowState(topLevelWidget()->windowState() & ~Qt::WindowFullScreen); // reset } else { topLevelWidget()->setWindowState(topLevelWidget()->windowState() | Qt::WindowFullScreen); // set } } void KStars::slotClearAllTrails() { //Exclude object with temporary trail SkyObject *exOb(nullptr); if (map()->focusObject() && map()->focusObject()->isSolarSystem() && data()->temporaryTrail) { exOb = map()->focusObject(); } TrailObject::clearTrailsExcept(exOb); map()->forceUpdate(); } //toggle display of GUI Items on/off void KStars::slotShowGUIItem(bool show) { //Toolbars if (sender() == actionCollection()->action("show_statusBar")) { Options::setShowStatusBar(show); statusBar()->setVisible(show); } if (sender() == actionCollection()->action("show_sbAzAlt")) { Options::setShowAltAzField(show); if (!show) AltAzField.hide(); else AltAzField.show(); } if (sender() == actionCollection()->action("show_sbRADec")) { Options::setShowRADecField(show); if (!show) RADecField.hide(); else RADecField.show(); } if (sender() == actionCollection()->action("show_sbJ2000RADec")) { Options::setShowJ2000RADecField(show); if (!show) J2000RADecField.hide(); else J2000RADecField.show(); } } void KStars::addColorMenuItem(const QString &name, const QString &actionName) { KToggleAction *kta = actionCollection()->add(actionName); kta->setText(name); kta->setObjectName(actionName); kta->setActionGroup(cschemeGroup); colorActionMenu->addAction(kta); KConfigGroup cg = KSharedConfig::openConfig()->group("Colors"); if (actionName.mid(3) == cg.readEntry("ColorSchemeFile", "moonless-night.colors").remove(".colors")) { kta->setChecked(true); } connect(kta, SIGNAL(toggled(bool)), this, SLOT(slotColorScheme())); } void KStars::removeColorMenuItem(const QString &actionName) { qCDebug(KSTARS) << "removing " << actionName; colorActionMenu->removeAction(actionCollection()->action(actionName)); } void KStars::slotAboutToQuit() { // Delete skymap. This required to run destructors and save // current state in the option. delete m_SkyMap; //Store Window geometry in Options object Options::setWindowWidth(width()); Options::setWindowHeight(height()); //explicitly save the colorscheme data to the config file data()->colorScheme()->saveToConfig(); //synch the config file with the Config object writeConfig(); //Terminate Child Processes if on OS X #ifdef Q_OS_OSX QProcess *quit = new QProcess(this); quit->start("killall kdeinit5"); quit->waitForFinished(1000); quit->start("killall klauncher"); quit->waitForFinished(1000); quit->start("killall kioslave"); quit->waitForFinished(1000); quit->start("killall kio_http_cache_cleaner"); quit->waitForFinished(1000); delete quit; #endif } void KStars::slotShowPositionBar(SkyPoint *p) { if (Options::showAltAzField()) { dms a = p->alt(); if (Options::useAltAz()) a = p->altRefracted(); QString s = QString("%1, %2").arg(p->az().toDMSString(true), //true: force +/- symbol a.toDMSString(true)); //true: force +/- symbol //statusBar()->changeItem( s, 1 ); AltAzField.setText(s); } if (Options::showRADecField()) { KStarsDateTime lastUpdate; lastUpdate.setDJD(KStarsData::Instance()->updateNum()->getJD()); QString sEpoch = QString::number(lastUpdate.epoch(), 'f', 1); QString s = QString("%1, %2 (J%3)") .arg(p->ra().toHMSString(), p->dec().toDMSString(true), sEpoch); //true: force +/- symbol //statusBar()->changeItem( s, 2 ); RADecField.setText(s); } if (Options::showJ2000RADecField()) { SkyPoint p0; p0 = p->deprecess(KStarsData::Instance()->updateNum()); // deprecess to update RA0/Dec0 from RA/Dec QString s = QString("%1, %2 (J2000)") .arg(p0.ra().toHMSString(), p0.dec().toDMSString(true)); //true: force +/- symbol //statusBar()->changeItem( s, 2 ); J2000RADecField.setText(s); } } void KStars::slotUpdateComets() { data()->skyComposite()->solarSystemComposite()->cometsComponent()->updateDataFile(); } void KStars::slotUpdateAsteroids() { data()->skyComposite()->solarSystemComposite()->asteroidsComponent()->updateDataFile(); } void KStars::slotUpdateSupernovae() { data()->skyComposite()->supernovaeComponent()->slotTriggerDataFileUpdate(); } void KStars::slotUpdateSatellites() { data()->skyComposite()->satellites()->updateTLEs(); } void KStars::slotAddDeepSkyObject() { if (!m_addDSODialog) { Q_ASSERT(data() && data()->skyComposite() && data()->skyComposite()->manualAdditionsComponent()); m_addDSODialog = new AddDeepSkyObject(this, data()->skyComposite()->manualAdditionsComponent()); } m_addDSODialog->show(); } void KStars::slotConfigureNotifications() { #ifdef HAVE_NOTIFYCONFIG KNotifyConfigWidget::configure(this); #endif } diff --git a/kstars/kstarsdata.cpp b/kstars/kstarsdata.cpp index 2bd2c4e82..af568a944 100644 --- a/kstars/kstarsdata.cpp +++ b/kstars/kstarsdata.cpp @@ -1,1460 +1,1460 @@ /*************************************************************************** kstarsdata.cpp - 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. * * * ***************************************************************************/ #include "kstarsdata.h" #include "fov.h" #include "ksutils.h" #include "Options.h" #include "auxiliary/kspaths.h" #include "skycomponents/supernovaecomponent.h" #include "skycomponents/skymapcomposite.h" #ifndef KSTARS_LITE #include "imageexporter.h" #include "kstars.h" #include "observinglist.h" #include "skymap.h" #include "dialogs/detaildialog.h" #include "oal/execute.h" #endif #ifndef KSTARS_LITE #include #endif #include #include #include "kstars_debug.h" namespace { // Report fatal error during data loading to user // Calls QApplication::exit void fatalErrorMessage(QString fname) { #ifndef KSTARS_LITE - KMessageBox::sorry(0, + KMessageBox::sorry(nullptr, i18n("The file %1 could not be found. " "KStars cannot run properly without this file. " "KStars searches for this file in following locations:\n\n\t" "%2\n\n" "It appears that your setup is broken.", fname, QStandardPaths::standardLocations(QStandardPaths::DataLocation).join("\n\t")), i18n("Critical File Not Found: %1", fname)); // FIXME: Must list locations depending on file type #endif qDebug() << i18n("Critical File Not Found: %1", fname); qApp->exit(1); } // Report non-fatal error during data loading to user and ask // whether he wants to continue. // Calls QApplication::exit if he don't #if 0 bool nonFatalErrorMessage(QString fname) { #ifdef KSTARS_LITE Q_UNUSED(fname) #else int res = KMessageBox::warningContinueCancel(0, i18n("The file %1 could not be found. " "KStars can still run without this file. " "KStars search for this file in following locations:\n\n\t" "%2\n\n" "It appears that you setup is broken. Press Continue to run KStars without this file ", fname, QStandardPaths::standardLocations( QStandardPaths::DataLocation ).join("\n\t") ), i18n( "Non-Critical File Not Found: %1", fname )); // FIXME: Must list locations depending on file type if( res != KMessageBox::Continue ) qApp->exit(1); return res == KMessageBox::Continue; #endif return true; } #endif } -KStarsData *KStarsData::pinstance = 0; +KStarsData *KStarsData::pinstance = nullptr; KStarsData *KStarsData::Create() { // This method should never be called twice within a run, since a // lot of the code assumes that KStarsData, once created, is never // destroyed. They maintain local copies of KStarsData::Instance() // for efficiency (maybe this should change, but it is not // required to delete and reinstantiate KStarsData). Thus, when we // call this method, pinstance MUST be zero, i.e. this must be the // first (and last) time we are calling it. -- asimha Q_ASSERT(!pinstance); delete pinstance; pinstance = new KStarsData(); return pinstance; } KStarsData::KStarsData() : m_Geo(dms(0), dms(0)), m_ksuserdb(), m_catalogdb(), temporaryTrail(false), //locale( new KLocale( "kstars" ) ), m_preUpdateID(0), m_updateID(0), m_preUpdateNumID(0), m_updateNumID(0), m_preUpdateNum(J2000), m_updateNum(J2000) { #ifndef KSTARS_LITE m_LogObject.reset(new OAL::Log); #endif // at startup times run forward setTimeDirection(0.0); } KStarsData::~KStarsData() { Q_ASSERT(pinstance); //delete locale; qDeleteAll(geoList); geoList.clear(); qDeleteAll(ADVtreeList); ADVtreeList.clear(); pinstance = nullptr; } bool KStarsData::initialize() { //Initialize CatalogDB// catalogdb()->Initialize(); //Load Time Zone Rules// emit progressText(i18n("Reading time zone rules")); if (!readTimeZoneRulebook()) { fatalErrorMessage("TZrules.dat"); return false; } //Load Cities// emit progressText(i18n("Loading city data")); if (!readCityData()) { fatalErrorMessage("citydb.sqlite"); return false; } //Initialize User Database// emit progressText(i18n("Loading User Information")); m_ksuserdb.Initialize(); //Initialize SkyMapComposite// emit progressText(i18n("Loading sky objects")); m_SkyComposite.reset(new SkyMapComposite()); //Load Image URLs// //#ifndef Q_OS_ANDROID //On Android these 2 calls produce segfault. WARNING emit progressText(i18n("Loading Image URLs")); //if( !readURLData( "image_url.dat", 0 ) && !nonFatalErrorMessage( "image_url.dat" ) ) // return false; QtConcurrent::run(this, &KStarsData::readURLData, QString("image_url.dat"), 0, false); //Load Information URLs// emit progressText(i18n("Loading Information URLs")); //if( !readURLData( "info_url.dat", 1 ) && !nonFatalErrorMessage( "info_url.dat" ) ) // return false; QtConcurrent::run(this, &KStarsData::readURLData, QString("info_url.dat"), 1, false); //#endif //emit progressText( i18n("Loading Variable Stars" ) ); #ifndef KSTARS_LITE //Initialize Observing List m_ObservingList = new ObservingList(); #endif readUserLog(); #ifndef KSTARS_LITE readADVTreeData(); #endif return true; } void KStarsData::updateTime(GeoLocation *geo, const bool automaticDSTchange) { // sync LTime with the simulation clock LTime = geo->UTtoLT(ut()); syncLST(); //Only check DST if (1) TZrule is not the empty rule, and (2) if we have crossed //the DST change date/time. if (!geo->tzrule()->isEmptyRule()) { if (TimeRunsForward) { // timedirection is forward // DST change happens if current date is bigger than next calculated dst change if (ut() > NextDSTChange) resetToNewDST(geo, automaticDSTchange); } else { // timedirection is backward // DST change happens if current date is smaller than next calculated dst change if (ut() < NextDSTChange) resetToNewDST(geo, automaticDSTchange); } } KSNumbers num(ut().djd()); if (std::abs(ut().djd() - LastNumUpdate.djd()) > 1.0) { LastNumUpdate = KStarsDateTime(ut().djd()); m_preUpdateNumID++; m_preUpdateNum = KSNumbers(num); skyComposite()->update(&num); } if (std::abs(ut().djd() - LastPlanetUpdate.djd()) > 0.01) { LastPlanetUpdate = KStarsDateTime(ut().djd()); skyComposite()->updateSolarSystemBodies(&num); } // Moon moves ~30 arcmin/hr, so update its position every minute. if (std::abs(ut().djd() - LastMoonUpdate.djd()) > 0.00069444) { LastMoonUpdate = ut(); skyComposite()->updateMoons(&num); } //Update Alt/Az coordinates. Timescale varies with zoom level //If Clock is in Manual Mode, always update. (?) if (std::abs(ut().djd() - LastSkyUpdate.djd()) > 0.1 / Options::zoomFactor() || clock()->isManualMode()) { LastSkyUpdate = ut(); m_preUpdateID++; skyComposite() ->update(); //omit KSNumbers arg == just update Alt/Az coords // <-- Eh? -- asimha. Looks like this behavior / ideology has changed drastically. emit skyUpdate(clock()->isManualMode()); } } void KStarsData::syncUpdateIDs() { m_updateID = m_preUpdateID; if (m_updateNumID == m_preUpdateNumID) return; m_updateNumID = m_preUpdateNumID; m_updateNum = KSNumbers(m_preUpdateNum); } unsigned int KStarsData::incUpdateID() { m_preUpdateID++; m_preUpdateNumID++; syncUpdateIDs(); return m_updateID; } void KStarsData::setFullTimeUpdate() { //Set the update markers to invalid dates to trigger updates in each category LastSkyUpdate = KStarsDateTime(QDateTime()); LastPlanetUpdate = KStarsDateTime(QDateTime()); LastMoonUpdate = KStarsDateTime(QDateTime()); LastNumUpdate = KStarsDateTime(QDateTime()); } void KStarsData::syncLST() { LST = geo()->GSTtoLST(ut().gst()); } void KStarsData::changeDateTime(const KStarsDateTime &newDate) { //Turn off animated slews for the next time step. setSnapNextFocus(); clock()->setUTC(newDate); LTime = geo()->UTtoLT(ut()); //set local sideral time syncLST(); //Make sure Numbers, Moon, planets, and sky objects are updated immediately setFullTimeUpdate(); // reset tzrules data with new local time and time direction (forward or backward) geo()->tzrule()->reset_with_ltime(LTime, geo()->TZ0(), isTimeRunningForward()); // reset next dst change time setNextDSTChange(geo()->tzrule()->nextDSTChange()); } void KStarsData::resetToNewDST(GeoLocation *geo, const bool automaticDSTchange) { // reset tzrules data with local time, timezone offset and time direction (forward or backward) // force a DST change with option true for 3. parameter geo->tzrule()->reset_with_ltime(LTime, geo->TZ0(), TimeRunsForward, automaticDSTchange); // reset next DST change time setNextDSTChange(geo->tzrule()->nextDSTChange()); //reset LTime, because TZoffset has changed LTime = geo->UTtoLT(ut()); } void KStarsData::setTimeDirection(float scale) { TimeRunsForward = scale >= 0; } GeoLocation *KStarsData::locationNamed(const QString &city, const QString &province, const QString &country) { foreach (GeoLocation *loc, geoList) { if (loc->translatedName() == city && (province.isEmpty() || loc->translatedProvince() == province) && (country.isEmpty() || loc->translatedCountry() == country)) { return loc; } } - return 0; + return nullptr; } void KStarsData::setLocationFromOptions() { setLocation(GeoLocation(dms(Options::longitude()), dms(Options::latitude()), Options::cityName(), Options::provinceName(), Options::countryName(), Options::timeZone(), &(Rulebook[Options::dST()]), false, 4, Options::elevation())); } void KStarsData::setLocation(const GeoLocation &l) { m_Geo = GeoLocation(l); if (m_Geo.lat()->Degrees() >= 90.0) m_Geo.setLat(dms(89.99)); if (m_Geo.lat()->Degrees() <= -90.0) m_Geo.setLat(dms(-89.99)); //store data in the Options objects Options::setCityName(m_Geo.name()); Options::setProvinceName(m_Geo.province()); Options::setCountryName(m_Geo.country()); Options::setTimeZone(m_Geo.TZ0()); Options::setElevation(m_Geo.height()); Options::setLongitude(m_Geo.lng()->Degrees()); Options::setLatitude(m_Geo.lat()->Degrees()); // set the rule from rulebook foreach (const QString &key, Rulebook.keys()) { if (!key.isEmpty() && m_Geo.tzrule()->equals(&Rulebook[key])) Options::setDST(key); } emit geoChanged(); } SkyObject *KStarsData::objectNamed(const QString &name) { if ((name == "star") || (name == "nothing") || name.isEmpty()) - return 0; + return nullptr; return skyComposite()->findByName(name); } bool KStarsData::readCityData() { QSqlDatabase citydb = QSqlDatabase::addDatabase("QSQLITE", "citydb"); QString dbfile = KSPaths::locate(QStandardPaths::GenericDataLocation, "citydb.sqlite"); citydb.setDatabaseName(dbfile); if (citydb.open() == false) { qCCritical(KSTARS) << "Unable to open city database file " << dbfile << citydb.lastError().text(); return false; } QSqlQuery get_query(citydb); //get_query.prepare("SELECT * FROM city"); if (!get_query.exec("SELECT * FROM city")) { qCCritical(KSTARS) << get_query.lastError(); return false; } bool citiesFound = false; // get_query.size() always returns -1 so we set citiesFound if at least one city is found while (get_query.next()) { citiesFound = true; QString name = get_query.value(1).toString(); QString province = get_query.value(2).toString(); QString country = get_query.value(3).toString(); dms lat = dms(get_query.value(4).toString()); dms lng = dms(get_query.value(5).toString()); double TZ = get_query.value(6).toDouble(); TimeZoneRule *TZrule = &(Rulebook[get_query.value(7).toString()]); // appends city names to list geoList.append(new GeoLocation(lng, lat, name, province, country, TZ, TZrule, true)); } citydb.close(); // Reading local database QSqlDatabase mycitydb = QSqlDatabase::addDatabase("QSQLITE", "mycitydb"); dbfile = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + "mycitydb.sqlite"; if (QFile::exists(dbfile)) { mycitydb.setDatabaseName(dbfile); if (mycitydb.open()) { QSqlQuery get_query(mycitydb); if (!get_query.exec("SELECT * FROM city")) { qDebug() << get_query.lastError(); return false; } while (get_query.next()) { QString name = get_query.value(1).toString(); QString province = get_query.value(2).toString(); QString country = get_query.value(3).toString(); dms lat = dms(get_query.value(4).toString()); dms lng = dms(get_query.value(5).toString()); double TZ = get_query.value(6).toDouble(); TimeZoneRule *TZrule = &(Rulebook[get_query.value(7).toString()]); // appends city names to list geoList.append(new GeoLocation(lng, lat, name, province, country, TZ, TZrule, false)); } mycitydb.close(); } } return citiesFound; } bool KStarsData::readTimeZoneRulebook() { QFile file; if (KSUtils::openDataFile(file, "TZrules.dat")) { QTextStream stream(&file); while (!stream.atEnd()) { QString line = stream.readLine().trimmed(); if (line.length() && !line.startsWith('#')) //ignore commented and blank lines { QStringList fields = line.split(' ', QString::SkipEmptyParts); QString id = fields[0]; QTime stime = QTime(fields[3].leftRef(fields[3].indexOf(':')).toInt(), fields[3].midRef(fields[3].indexOf(':') + 1, fields[3].length()).toInt()); QTime rtime = QTime(fields[6].leftRef(fields[6].indexOf(':')).toInt(), fields[6].midRef(fields[6].indexOf(':') + 1, fields[6].length()).toInt()); Rulebook[id] = TimeZoneRule(fields[1], fields[2], stime, fields[4], fields[5], rtime); } } return true; } else { return false; } } bool KStarsData::openUrlFile(const QString &urlfile, QFile &file) { //QFile file; QString localFile; bool fileFound = false; QFile localeFile; //if ( locale->language() != "en_US" ) if (QLocale().language() != QLocale::English) //localFile = locale->language() + '/' + urlfile; localFile = QLocale().languageToString(QLocale().language()) + '/' + urlfile; if (!localFile.isEmpty() && KSUtils::openDataFile(file, localFile)) { fileFound = true; } else { // Try to load locale file, if not successful, load regular urlfile and then copy it to locale. file.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + urlfile); if (file.open(QIODevice::ReadOnly)) { //local file found. Now, if global file has newer timestamp, then merge the two files. //First load local file into QStringList bool newDataFound(false); QStringList urlData; QTextStream lStream(&file); while (!lStream.atEnd()) urlData.append(lStream.readLine()); //Find global file(s) in findAllResources() list. QFileInfo fi_local(file.fileName()); QStringList flist = KSPaths::locateAll(QStandardPaths::DataLocation, urlfile); for (int i = 0; i < flist.size(); i++) { if (flist[i] != file.fileName()) { QFileInfo fi_global(flist[i]); //Is this global file newer than the local file? if (fi_global.lastModified() > fi_local.lastModified()) { //Global file has newer timestamp than local. Add lines in global file that don't already exist in local file. //be smart about this; in some cases the URL is updated but the image is probably the same if its //label string is the same. So only check strings up to last ":" QFile globalFile(flist[i]); if (globalFile.open(QIODevice::ReadOnly)) { QTextStream gStream(&globalFile); while (!gStream.atEnd()) { QString line = gStream.readLine(); //If global-file line begins with "XXX:" then this line should be removed from the local file. if (line.startsWith(QLatin1String("XXX:")) && urlData.contains(line.mid(4))) { urlData.removeAt(urlData.indexOf(line.mid(4))); } else { //does local file contain the current global file line, up to second ':' ? bool linefound(false); for (int j = 0; j < urlData.size(); ++j) { if (urlData[j].contains(line.left(line.indexOf(':', line.indexOf(':') + 1)))) { //replace line in urlData with its equivalent in the newer global file. urlData.replace(j, line); if (!newDataFound) newDataFound = true; linefound = true; break; } } if (!linefound) { urlData.append(line); if (!newDataFound) newDataFound = true; } } } } } } } file.close(); //(possibly) write appended local file if (newDataFound) { if (file.open(QIODevice::WriteOnly)) { QTextStream outStream(&file); for (int i = 0; i < urlData.size(); i++) { outStream << urlData[i] << endl; } file.close(); } } if (file.open(QIODevice::ReadOnly)) fileFound = true; } else { if (KSUtils::openDataFile(file, urlfile)) { if (QLocale().language() != QLocale::English) qDebug() << "No localized URL file; using default English file."; // we found urlfile, we need to copy it to locale localeFile.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + urlfile); if (localeFile.open(QIODevice::WriteOnly)) { QTextStream readStream(&file); QTextStream writeStream(&localeFile); while (!readStream.atEnd()) { QString line = readStream.readLine(); if (!line.startsWith(QLatin1String("XXX:"))) //do not write "deleted" lines writeStream << line << endl; } localeFile.close(); file.reset(); } else { qDebug() << "Failed to copy default URL file to locale folder, modifying default object links is " "not possible"; } fileFound = true; } } } return fileFound; } // FIXME: This is a significant contributor to KStars start-up time bool KStarsData::readURLData(const QString &urlfile, int type, bool deepOnly) { #ifndef KSTARS_LITE if (KStars::Closing) return true; #endif QFile file; if (!openUrlFile(urlfile, file)) return false; QTextStream stream(&file); while (!stream.atEnd()) { QString line = stream.readLine(); //ignore comment lines if (!line.startsWith('#')) { #ifndef KSTARS_LITE if (KStars::Closing) { file.close(); return true; } #endif int idx = line.indexOf(':'); QString name = line.left(idx); if (name == "XXX") continue; QString sub = line.mid(idx + 1); idx = sub.indexOf(':'); QString title = sub.left(idx); QString url = sub.mid(idx + 1); // Dirty hack to fix things up for planets SkyObject *o; if (name == "Mercury" || name == "Venus" || name == "Mars" || name == "Jupiter" || name == "Saturn" || name == "Uranus" || name == "Neptune" /* || name == "Pluto" */) o = skyComposite()->findByName(i18n(name.toLocal8Bit().data())); else o = skyComposite()->findByName(name); if (!o) { qCWarning(KSTARS) << i18n("Object named %1 not found", name); } else { if (!deepOnly || (o->type() > 2 && o->type() < 9)) { if (type == 0) //image URL { o->ImageList().append(url); o->ImageTitle().append(title); } else if (type == 1) //info URL { o->InfoList().append(url); o->InfoTitle().append(title); } } } } } file.close(); return true; } // FIXME: Improve the user log system // Note: It might be very nice to keep the log in plaintext files, for // portability, human-readability, and greppability. However, it takes // a lot of time to parse and look up, is very messy from the // reliability and programming point of view, needs to be parsed at // start, can become corrupt easily because of a missing bracket... // An SQLite database is a good compromise. A user can easily view it // using an SQLite browser. There is no need to read at start-up, one // can read the log when required. Easy to edit logs / update logs // etc. Will not become corrupt. Needn't be parsed. // However, IMHO, it is best to put these kinds of things in separate // databases, instead of unifying them as a table under the user // database. This ensures portability and a certain robustness that if // a user opens it, they cannot incorrectly edit a part of the DB they // did not intend to edit. // --asimha 2016 Aug 17 // FIXME: This is a significant contributor to KStars startup time. bool KStarsData::readUserLog() { QFile file; QString buffer; QString sub, name, data; if (!KSUtils::openDataFile(file, "userlog.dat")) return false; QTextStream stream(&file); if (!stream.atEnd()) buffer = stream.readAll(); while (!buffer.isEmpty()) { int startIndex, endIndex; startIndex = buffer.indexOf(QLatin1String("[KSLABEL:")); sub = buffer.mid(startIndex); // FIXME: This is inefficient because we are making a copy of a huge string! endIndex = sub.indexOf(QLatin1String("[KSLogEnd]")); // Read name after KSLABEL identifer name = sub.mid(startIndex + 9, sub.indexOf(']') - (startIndex + 9)); // Read data and skip new line data = sub.mid(sub.indexOf(']') + 2, endIndex - (sub.indexOf(']') + 2)); buffer = buffer.mid(endIndex + 11); //Find the sky object named 'name'. //Note that ObjectNameList::find() looks for the ascii representation //of star genetive names, so stars are identified that way in the user log. SkyObject *o = skyComposite()->findByName(name); if (!o) { qWarning() << name << " not found"; } else { o->userLog() = data; } } // end while file.close(); return true; } bool KStarsData::readADVTreeData() { QFile file; QString Interface; QString Name, Link, subName; if (!KSUtils::openDataFile(file, "advinterface.dat")) return false; QTextStream stream(&file); QString Line; while (!stream.atEnd()) { int Type, interfaceIndex; Line = stream.readLine(); if (Line.startsWith(QLatin1String("[KSLABEL]"))) { Name = Line.mid(9); Type = 0; } else if (Line.startsWith(QLatin1String("[END]"))) Type = 1; else if (Line.startsWith(QLatin1String("[KSINTERFACE]"))) { Interface = Line.mid(13); continue; } else { int idx = Line.indexOf(':'); Name = Line.left(idx); Link = Line.mid(idx + 1); // Link is empty, using Interface instead if (Link.isEmpty()) { Link = Interface; subName = Name; interfaceIndex = Link.indexOf(QLatin1String("KSINTERFACE")); Link.remove(interfaceIndex, 11); Link = Link.insert(interfaceIndex, subName.replace(' ', '+')); } Type = 2; } ADVTreeData *ADVData = new ADVTreeData; ADVData->Name = Name; ADVData->Link = Link; ADVData->Type = Type; ADVtreeList.append(ADVData); } return true; } //There's a lot of code duplication here, but it's not avoidable because //this function is only called from main.cpp when the user is using //"dump" mode to produce an image from the command line. In this mode, //there is no KStars object, so none of the DBus functions can be called //directly. bool KStarsData::executeScript(const QString &scriptname, SkyMap *map) { #ifndef KSTARS_LITE int cmdCount(0); QFile f(scriptname); if (!f.open(QIODevice::ReadOnly)) { qDebug() << "Could not open file " << f.fileName(); return false; } QTextStream istream(&f); while (!istream.atEnd()) { QString line = istream.readLine(); line.remove("string:"); line.remove("int:"); line.remove("double:"); line.remove("bool:"); //find a dbus line and extract the function name and its arguments //The function name starts after the last occurrence of "org.kde.kstars." //or perhaps "org.kde.kstars.SimClock.". if (line.startsWith(QString("dbus-send"))) { QString funcprefix = "org.kde.kstars.SimClock."; int i = line.lastIndexOf(funcprefix); if (i >= 0) { i += funcprefix.length(); } else { funcprefix = "org.kde.kstars."; i = line.lastIndexOf(funcprefix); if (i >= 0) { i += funcprefix.length(); } } if (i < 0) { qWarning() << "Could not parse line: " << line; return false; } QStringList fn = line.mid(i).split(' '); //DEBUG //qDebug() << fn << endl; if (fn[0] == "lookTowards" && fn.size() >= 2) { double az(-1.0); QString arg = fn[1].toLower(); if (arg == "n" || arg == "north") az = 0.0; if (arg == "ne" || arg == "northeast") az = 45.0; if (arg == "e" || arg == "east") az = 90.0; if (arg == "se" || arg == "southeast") az = 135.0; if (arg == "s" || arg == "south") az = 180.0; if (arg == "sw" || arg == "southwest") az = 225.0; if (arg == "w" || arg == "west") az = 270.0; if (arg == "nw" || arg == "northwest") az = 335.0; if (az >= 0.0) { map->setFocusAltAz(dms(90.0), map->focus()->az()); map->focus()->HorizontalToEquatorial(&LST, geo()->lat()); map->setDestination(*map->focus()); cmdCount++; } if (arg == "z" || arg == "zenith") { map->setFocusAltAz(dms(90.0), map->focus()->az()); map->focus()->HorizontalToEquatorial(&LST, geo()->lat()); map->setDestination(*map->focus()); cmdCount++; } //try a named object. The name is everything after fn[0], //concatenated with spaces. fn.removeAll(fn.first()); QString objname = fn.join(" "); SkyObject *target = objectNamed(objname); if (target) { map->setFocus(target); map->focus()->EquatorialToHorizontal(&LST, geo()->lat()); map->setDestination(*map->focus()); cmdCount++; } } else if (fn[0] == "setRaDec" && fn.size() == 3) { bool ok(false); dms r(0.0), d(0.0); ok = r.setFromString(fn[1], false); //assume angle in hours if (ok) ok = d.setFromString(fn[2], true); //assume angle in degrees if (ok) { map->setFocus(r, d); map->focus()->EquatorialToHorizontal(&LST, geo()->lat()); cmdCount++; } } else if (fn[0] == "setAltAz" && fn.size() == 3) { bool ok(false); dms az(0.0), alt(0.0); ok = alt.setFromString(fn[1]); if (ok) ok = az.setFromString(fn[2]); if (ok) { map->setFocusAltAz(alt, az); map->focus()->HorizontalToEquatorial(&LST, geo()->lat()); cmdCount++; } } else if (fn[0] == "loadColorScheme") { fn.removeAll(fn.first()); QString csName = fn.join(" ").remove('\"'); qCDebug(KSTARS) << "Loading Color scheme: " << csName; QString filename = csName.toLower().trimmed(); bool ok(false); //Parse default names which don't follow the regular file-naming scheme if (csName == i18nc("use default color scheme", "Default Colors")) filename = "classic.colors"; if (csName == i18nc("use 'star chart' color scheme", "Star Chart")) filename = "chart.colors"; if (csName == i18nc("use 'night vision' color scheme", "Night Vision")) filename = "night.colors"; //Try the filename if it ends with ".colors" if (filename.endsWith(QLatin1String(".colors"))) ok = colorScheme()->load(filename); //If that didn't work, try assuming that 'name' is the color scheme name //convert it to a filename exactly as ColorScheme::save() does if (!ok) { if (!filename.isEmpty()) { for (int i = 0; i < filename.length(); ++i) if (filename.at(i) == ' ') filename.replace(i, 1, "-"); filename = filename.append(".colors"); ok = colorScheme()->load(filename); } if (!ok) qDebug() << QString("Unable to load color scheme named %1. Also tried %2.") .arg(csName, filename); } } else if (fn[0] == "zoom" && fn.size() == 2) { bool ok(false); double z = fn[1].toDouble(&ok); if (ok) { if (z > MAXZOOM) z = MAXZOOM; if (z < MINZOOM) z = MINZOOM; Options::setZoomFactor(z); cmdCount++; } } else if (fn[0] == "zoomIn") { if (Options::zoomFactor() < MAXZOOM) { Options::setZoomFactor(Options::zoomFactor() * DZOOM); cmdCount++; } } else if (fn[0] == "zoomOut") { if (Options::zoomFactor() > MINZOOM) { Options::setZoomFactor(Options::zoomFactor() / DZOOM); cmdCount++; } } else if (fn[0] == "defaultZoom") { Options::setZoomFactor(DEFAULTZOOM); cmdCount++; } else if (fn[0] == "setLocalTime" && fn.size() == 7) { bool ok(false); // min is a macro - use mnt int yr(0), mth(0), day(0), hr(0), mnt(0), sec(0); yr = fn[1].toInt(&ok); if (ok) mth = fn[2].toInt(&ok); if (ok) day = fn[3].toInt(&ok); if (ok) hr = fn[4].toInt(&ok); if (ok) mnt = fn[5].toInt(&ok); if (ok) sec = fn[6].toInt(&ok); if (ok) { changeDateTime(geo()->LTtoUT(KStarsDateTime(QDate(yr, mth, day), QTime(hr, mnt, sec)))); cmdCount++; } else { qWarning() << ki18n("Could not set time: %1 / %2 / %3 ; %4:%5:%6") .subs(day) .subs(mth) .subs(yr) .subs(hr) .subs(mnt) .subs(sec) .toString() << endl; } } else if (fn[0] == "changeViewOption" && fn.size() == 3) { bool bOk(false), dOk(false); //parse bool value bool bVal(false); if (fn[2].toLower() == "true") { bOk = true; bVal = true; } if (fn[2].toLower() == "false") { bOk = true; bVal = false; } if (fn[2] == "1") { bOk = true; bVal = true; } if (fn[2] == "0") { bOk = true; bVal = false; } //parse double value double dVal = fn[2].toDouble(&dOk); // FIXME: REGRESSION // if ( fn[1] == "FOVName" ) { Options::setFOVName( fn[2] ); cmdCount++; } // if ( fn[1] == "FOVSizeX" && dOk ) { Options::setFOVSizeX( (float)dVal ); cmdCount++; } // if ( fn[1] == "FOVSizeY" && dOk ) { Options::setFOVSizeY( (float)dVal ); cmdCount++; } // if ( fn[1] == "FOVShape" && nOk ) { Options::setFOVShape( nVal ); cmdCount++; } // if ( fn[1] == "FOVColor" ) { Options::setFOVColor( fn[2] ); cmdCount++; } if (fn[1] == "ShowStars" && bOk) { Options::setShowStars(bVal); cmdCount++; } if (fn[1] == "ShowMessier" && bOk) { Options::setShowMessier(bVal); cmdCount++; } if (fn[1] == "ShowMessierImages" && bOk) { Options::setShowMessierImages(bVal); cmdCount++; } if (fn[1] == "ShowCLines" && bOk) { Options::setShowCLines(bVal); cmdCount++; } if (fn[1] == "ShowCNames" && bOk) { Options::setShowCNames(bVal); cmdCount++; } if (fn[1] == "ShowNGC" && bOk) { Options::setShowNGC(bVal); cmdCount++; } if (fn[1] == "ShowIC" && bOk) { Options::setShowIC(bVal); cmdCount++; } if (fn[1] == "ShowMilkyWay" && bOk) { Options::setShowMilkyWay(bVal); cmdCount++; } if (fn[1] == "ShowEquatorialGrid" && bOk) { Options::setShowEquatorialGrid(bVal); cmdCount++; } if (fn[1] == "ShowHorizontalGrid" && bOk) { Options::setShowHorizontalGrid(bVal); cmdCount++; } if (fn[1] == "ShowEquator" && bOk) { Options::setShowEquator(bVal); cmdCount++; } if (fn[1] == "ShowEcliptic" && bOk) { Options::setShowEcliptic(bVal); cmdCount++; } if (fn[1] == "ShowHorizon" && bOk) { Options::setShowHorizon(bVal); cmdCount++; } if (fn[1] == "ShowGround" && bOk) { Options::setShowGround(bVal); cmdCount++; } if (fn[1] == "ShowSun" && bOk) { Options::setShowSun(bVal); cmdCount++; } if (fn[1] == "ShowMoon" && bOk) { Options::setShowMoon(bVal); cmdCount++; } if (fn[1] == "ShowMercury" && bOk) { Options::setShowMercury(bVal); cmdCount++; } if (fn[1] == "ShowVenus" && bOk) { Options::setShowVenus(bVal); cmdCount++; } if (fn[1] == "ShowMars" && bOk) { Options::setShowMars(bVal); cmdCount++; } if (fn[1] == "ShowJupiter" && bOk) { Options::setShowJupiter(bVal); cmdCount++; } if (fn[1] == "ShowSaturn" && bOk) { Options::setShowSaturn(bVal); cmdCount++; } if (fn[1] == "ShowUranus" && bOk) { Options::setShowUranus(bVal); cmdCount++; } if (fn[1] == "ShowNeptune" && bOk) { Options::setShowNeptune(bVal); cmdCount++; } //if ( fn[1] == "ShowPluto" && bOk ) { Options::setShowPluto( bVal ); cmdCount++; } if (fn[1] == "ShowAsteroids" && bOk) { Options::setShowAsteroids(bVal); cmdCount++; } if (fn[1] == "ShowComets" && bOk) { Options::setShowComets(bVal); cmdCount++; } if (fn[1] == "ShowSolarSystem" && bOk) { Options::setShowSolarSystem(bVal); cmdCount++; } if (fn[1] == "ShowDeepSky" && bOk) { Options::setShowDeepSky(bVal); cmdCount++; } if (fn[1] == "ShowSupernovae" && bOk) { Options::setShowSupernovae(bVal); cmdCount++; } if (fn[1] == "ShowStarNames" && bOk) { Options::setShowStarNames(bVal); cmdCount++; } if (fn[1] == "ShowStarMagnitudes" && bOk) { Options::setShowStarMagnitudes(bVal); cmdCount++; } if (fn[1] == "ShowAsteroidNames" && bOk) { Options::setShowAsteroidNames(bVal); cmdCount++; } if (fn[1] == "ShowCometNames" && bOk) { Options::setShowCometNames(bVal); cmdCount++; } if (fn[1] == "ShowPlanetNames" && bOk) { Options::setShowPlanetNames(bVal); cmdCount++; } if (fn[1] == "ShowPlanetImages" && bOk) { Options::setShowPlanetImages(bVal); cmdCount++; } if (fn[1] == "UseAltAz" && bOk) { Options::setUseAltAz(bVal); cmdCount++; } if (fn[1] == "UseRefraction" && bOk) { Options::setUseRefraction(bVal); cmdCount++; } if (fn[1] == "UseAutoLabel" && bOk) { Options::setUseAutoLabel(bVal); cmdCount++; } if (fn[1] == "UseAutoTrail" && bOk) { Options::setUseAutoTrail(bVal); cmdCount++; } if (fn[1] == "UseAnimatedSlewing" && bOk) { Options::setUseAnimatedSlewing(bVal); cmdCount++; } if (fn[1] == "FadePlanetTrails" && bOk) { Options::setFadePlanetTrails(bVal); cmdCount++; } if (fn[1] == "SlewTimeScale" && dOk) { Options::setSlewTimeScale(dVal); cmdCount++; } if (fn[1] == "ZoomFactor" && dOk) { Options::setZoomFactor(dVal); cmdCount++; } // if ( fn[1] == "MagLimitDrawStar" && dOk ) { Options::setMagLimitDrawStar( dVal ); cmdCount++; } if (fn[1] == "StarDensity" && dOk) { Options::setStarDensity(dVal); cmdCount++; } // if ( fn[1] == "MagLimitDrawStarZoomOut" && dOk ) { Options::setMagLimitDrawStarZoomOut( dVal ); cmdCount++; } if (fn[1] == "MagLimitDrawDeepSky" && dOk) { Options::setMagLimitDrawDeepSky(dVal); cmdCount++; } if (fn[1] == "MagLimitDrawDeepSkyZoomOut" && dOk) { Options::setMagLimitDrawDeepSkyZoomOut(dVal); cmdCount++; } if (fn[1] == "StarLabelDensity" && dOk) { Options::setStarLabelDensity(dVal); cmdCount++; } if (fn[1] == "MagLimitHideStar" && dOk) { Options::setMagLimitHideStar(dVal); cmdCount++; } if (fn[1] == "MagLimitAsteroid" && dOk) { Options::setMagLimitAsteroid(dVal); cmdCount++; } if (fn[1] == "AsteroidLabelDensity" && dOk) { Options::setAsteroidLabelDensity(dVal); cmdCount++; } if (fn[1] == "MaxRadCometName" && dOk) { Options::setMaxRadCometName(dVal); cmdCount++; } //these three are a "radio group" if (fn[1] == "UseLatinConstellationNames" && bOk) { Options::setUseLatinConstellNames(true); Options::setUseLocalConstellNames(false); Options::setUseAbbrevConstellNames(false); cmdCount++; } if (fn[1] == "UseLocalConstellationNames" && bOk) { Options::setUseLatinConstellNames(false); Options::setUseLocalConstellNames(true); Options::setUseAbbrevConstellNames(false); cmdCount++; } if (fn[1] == "UseAbbrevConstellationNames" && bOk) { Options::setUseLatinConstellNames(false); Options::setUseLocalConstellNames(false); Options::setUseAbbrevConstellNames(true); cmdCount++; } } else if (fn[0] == "setGeoLocation" && (fn.size() == 3 || fn.size() == 4)) { QString city(fn[1]), province, country(fn[2]); province.clear(); if (fn.size() == 4) { province = fn[2]; country = fn[3]; } bool cityFound(false); foreach (GeoLocation *loc, geoList) { if (loc->translatedName() == city && (province.isEmpty() || loc->translatedProvince() == province) && loc->translatedCountry() == country) { cityFound = true; setLocation(*loc); cmdCount++; break; } } if (!cityFound) qWarning() << i18n("Could not set location named %1, %2, %3", city, province, country); } } } //end while if (cmdCount) return true; #else Q_UNUSED(map) Q_UNUSED(scriptname) #endif return false; } void KStarsData::syncFOV() { visibleFOVs.clear(); // Add visible FOVs foreach (FOV *fov, availFOVs) { if (Options::fOVNames().contains(fov->name())) visibleFOVs.append(fov); } // Remove unavailable FOVs QSet names = QSet::fromList(Options::fOVNames()); QSet all; foreach (FOV *fov, visibleFOVs) { all.insert(fov->name()); } Options::setFOVNames(all.intersect(names).toList()); } #ifndef KSTARS_LITE // FIXME: Why does KStarsData store the Execute instance??? -- asimha Execute *KStarsData::executeSession() { if (!m_Execute.get()) m_Execute.reset(new Execute()); return m_Execute.get(); } // FIXME: Why does KStarsData store the ImageExporer instance??? KStarsData is supposed to work with no reference to KStars -- asimha ImageExporter *KStarsData::imageExporter() { if (!m_ImageExporter.get()) m_ImageExporter.reset(new ImageExporter(KStars::Instance())); return m_ImageExporter.get(); } #endif diff --git a/kstars/kstarsdbus.cpp b/kstars/kstarsdbus.cpp index b983dae10..faef16445 100644 --- a/kstars/kstarsdbus.cpp +++ b/kstars/kstarsdbus.cpp @@ -1,997 +1,997 @@ /*************************************************************************** kstarsdbus.cpp - description ------------------- begin : Son Apr 7 2002 copyright : (C) 2002 by Thomas Kabelmann email : tk78@gmx.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. * * * ***************************************************************************/ //KStars DBUS functions #include "kstars.h" #include "colorscheme.h" #include "eyepiecefield.h" #include "imageexporter.h" #include "ksdssdownloader.h" #include "kstarsdata.h" #include "observinglist.h" #include "Options.h" #include "skymap.h" #include "skycomponents/constellationboundarylines.h" #include "skycomponents/skymapcomposite.h" #include "skyobjects/deepskyobject.h" #include "skyobjects/ksplanetbase.h" #include "skyobjects/starobject.h" #include "tools/whatsinteresting/wiview.h" #ifdef HAVE_CFITSIO #include "fitsviewer/fitsviewer.h" #ifdef HAVE_INDI #include "ekos/ekosmanager.h" #endif #endif #include #include #include #include "kstars_debug.h" void KStars::setRaDec(double ra, double dec) { SkyPoint p(ra, dec); map()->setDestination(p); } void KStars::setAltAz(double alt, double az) { map()->setDestinationAltAz(dms(alt), dms(az)); } void KStars::lookTowards(const QString &direction) { QString dir = direction.toLower(); if (dir == i18n("zenith") || dir == "z") { actionCollection()->action("zenith")->trigger(); } else if (dir == i18n("north") || dir == "n") { actionCollection()->action("north")->trigger(); } else if (dir == i18n("east") || dir == "e") { actionCollection()->action("east")->trigger(); } else if (dir == i18n("south") || dir == "s") { actionCollection()->action("south")->trigger(); } else if (dir == i18n("west") || dir == "w") { actionCollection()->action("west")->trigger(); } else if (dir == i18n("northeast") || dir == "ne") { map()->stopTracking(); map()->clickedPoint()->setAlt(15.0); map()->clickedPoint()->setAz(45.0); map()->clickedPoint()->HorizontalToEquatorial(data()->lst(), data()->geo()->lat()); map()->slotCenter(); } else if (dir == i18n("southeast") || dir == "se") { map()->stopTracking(); map()->clickedPoint()->setAlt(15.0); map()->clickedPoint()->setAz(135.0); map()->clickedPoint()->HorizontalToEquatorial(data()->lst(), data()->geo()->lat()); map()->slotCenter(); } else if (dir == i18n("southwest") || dir == "sw") { map()->stopTracking(); map()->clickedPoint()->setAlt(15.0); map()->clickedPoint()->setAz(225.0); map()->clickedPoint()->HorizontalToEquatorial(data()->lst(), data()->geo()->lat()); map()->slotCenter(); } else if (dir == i18n("northwest") || dir == "nw") { map()->stopTracking(); map()->clickedPoint()->setAlt(15.0); map()->clickedPoint()->setAz(315.0); map()->clickedPoint()->HorizontalToEquatorial(data()->lst(), data()->geo()->lat()); map()->slotCenter(); } else { SkyObject *target = data()->objectNamed(direction); if (target != nullptr) { map()->setClickedObject(target); map()->setClickedPoint(target); map()->slotCenter(); } } } void KStars::addLabel(const QString &name) { SkyObject *target = data()->objectNamed(name); if (target != nullptr) { data()->skyComposite()->addNameLabel(target); map()->forceUpdate(); } } void KStars::removeLabel(const QString &name) { SkyObject *target = data()->objectNamed(name); if (target != nullptr) { data()->skyComposite()->removeNameLabel(target); map()->forceUpdate(); } } void KStars::addTrail(const QString &name) { TrailObject *target = dynamic_cast(data()->objectNamed(name)); if (target) { target->addToTrail(); map()->forceUpdate(); } } void KStars::removeTrail(const QString &name) { TrailObject *target = dynamic_cast(data()->objectNamed(name)); if (target) { target->clearTrail(); map()->forceUpdate(); } } void KStars::zoom(double z) { map()->setZoomFactor(z); } void KStars::zoomIn() { map()->slotZoomIn(); } void KStars::zoomOut() { map()->slotZoomOut(); } void KStars::defaultZoom() { map()->slotZoomDefault(); } void KStars::setLocalTime(int yr, int mth, int day, int hr, int min, int sec) { data()->changeDateTime(data()->geo()->LTtoUT(KStarsDateTime(QDate(yr, mth, day), QTime(hr, min, sec)))); } void KStars::setTimeToNow() { slotSetTimeToNow(); } void KStars::waitFor(double sec) { QTime tm; tm.start(); while (tm.elapsed() < int(1000. * sec)) { qApp->processEvents(); } } void KStars::waitForKey(const QString &k) { data()->resumeKey = QKeySequence::fromString(k); if (!data()->resumeKey.isEmpty()) { //When the resumeKey is pressed, resumeKey is set to empty while (!data()->resumeKey.isEmpty()) qApp->processEvents(); } else { qDebug() << "Error [D-Bus waitForKey()]: Invalid key requested."; } } void KStars::setTracking(bool track) { if (track != Options::isTracking()) slotTrack(); } void KStars::popupMessage(int /*x*/, int /*y*/, const QString & /*message*/) { //Show a small popup window at (x,y) with a text message } void KStars::drawLine(int /*x1*/, int /*y1*/, int /*x2*/, int /*y2*/, int /*speed*/) { //Draw a line on the skymap display } bool KStars::setGeoLocation(const QString &city, const QString &province, const QString &country) { //Set the geographic location bool cityFound(false); foreach (GeoLocation *loc, data()->geoList) { if (loc->translatedName() == city && (province.isEmpty() || loc->translatedProvince() == province) && loc->translatedCountry() == country) { cityFound = true; data()->setLocation(*loc); //configure time zone rule KStarsDateTime ltime = loc->UTtoLT(data()->ut()); loc->tzrule()->reset_with_ltime(ltime, loc->TZ0(), data()->isTimeRunningForward()); data()->setNextDSTChange(loc->tzrule()->nextDSTChange()); //reset LST data()->syncLST(); //make sure planets, etc. are updated immediately data()->setFullTimeUpdate(); // If the sky is in Horizontal mode and not tracking, reset focus such that // Alt/Az remain constant. if (!Options::isTracking() && Options::useAltAz()) { map()->focus()->HorizontalToEquatorial(data()->lst(), data()->geo()->lat()); } // recalculate new times and objects data()->setSnapNextFocus(); updateTime(); //no need to keep looking, we're done. break; } } if (!cityFound) { if (province.isEmpty()) qDebug() << QString("Error [D-Bus setGeoLocation]: city %1, %2 not found in database.").arg(city, country); else qDebug() << QString("Error [D-Bus setGeoLocation]: city %1, %2, %3 not found in database.") .arg(city, province, country); } return cityFound; } void KStars::readConfig() { //Load config file values into Options object Options::self()->load(); applyConfig(); //Reset date, if one was stored if (data()->StoredDate.isValid()) { data()->changeDateTime(data()->geo()->LTtoUT(data()->StoredDate)); data()->StoredDate = KStarsDateTime(QDateTime()); //invalidate StoredDate } map()->forceUpdate(); } void KStars::writeConfig() { Options::self()->save(); //Store current simulation time data()->StoredDate = data()->lt(); } QString KStars::getOption(const QString &name) { //Some config items are not stored in the Options object while //the program is running; catch these here and returntheir current value. if (name == "FocusRA") { return QString::number(map()->focus()->ra().Hours(), 'f', 6); } if (name == "FocusDec") { return QString::number(map()->focus()->dec().Degrees(), 'f', 6); } KConfigSkeletonItem *it = Options::self()->findItem(name); if (it) return it->property().toString(); else return QString(); } void KStars::changeViewOption(const QString &op, const QString &val) { bool bOk(false), dOk(false); //parse bool value bool bVal(false); if (val.toLower() == "true") { bOk = true; bVal = true; } if (val.toLower() == "false") { bOk = true; bVal = false; } if (val == "1") { bOk = true; bVal = true; } if (val == "0") { bOk = true; bVal = false; } //parse double value double dVal = val.toDouble(&dOk); //[GUI] if (op == "ShowInfoBoxes" && bOk) Options::setShowInfoBoxes(bVal); if (op == "ShowTimeBox" && bOk) Options::setShowTimeBox(bVal); if (op == "ShowGeoBox" && bOk) Options::setShowGeoBox(bVal); if (op == "ShowFocusBox" && bOk) Options::setShowFocusBox(bVal); if (op == "ShadeTimeBox" && bOk) Options::setShadeTimeBox(bVal); if (op == "ShadeGeoBox" && bOk) Options::setShadeGeoBox(bVal); if (op == "ShadeFocusBox" && bOk) Options::setShadeFocusBox(bVal); //[View] // FIXME: REGRESSION // if ( op == "FOVName" ) Options::setFOVName( val ); // if ( op == "FOVSizeX" && dOk ) Options::setFOVSizeX( (float)dVal ); // if ( op == "FOVSizeY" && dOk ) Options::setFOVSizeY( (float)dVal ); // if ( op == "FOVShape" && nOk ) Options::setFOVShape( nVal ); // if ( op == "FOVColor" ) Options::setFOVColor( val ); if (op == "ShowStars" && bOk) Options::setShowStars(bVal); if (op == "ShowMessier" && bOk) Options::setShowMessier(bVal); if (op == "ShowMessierImages" && bOk) Options::setShowMessierImages(bVal); if (op == "ShowNGC" && bOk) Options::setShowNGC(bVal); if (op == "ShowIC" && bOk) Options::setShowIC(bVal); if (op == "ShowCLines" && bOk) Options::setShowCLines(bVal); if (op == "ShowCBounds" && bOk) Options::setShowCBounds(bVal); if (op == "ShowCNames" && bOk) Options::setShowCNames(bVal); if (op == "ShowMilkyWay" && bOk) Options::setShowMilkyWay(bVal); if (op == "AutoSelectGrid" && bOk) Options::setAutoSelectGrid(bVal); if (op == "ShowEquatorialGrid" && bOk) Options::setShowEquatorialGrid(bVal); if (op == "ShowHorizontalGrid" && bOk) Options::setShowHorizontalGrid(bVal); if (op == "ShowEquator" && bOk) Options::setShowEquator(bVal); if (op == "ShowEcliptic" && bOk) Options::setShowEcliptic(bVal); if (op == "ShowHorizon" && bOk) Options::setShowHorizon(bVal); if (op == "ShowGround" && bOk) Options::setShowGround(bVal); if (op == "ShowSun" && bOk) Options::setShowSun(bVal); if (op == "ShowMoon" && bOk) Options::setShowMoon(bVal); if (op == "ShowMercury" && bOk) Options::setShowMercury(bVal); if (op == "ShowVenus" && bOk) Options::setShowVenus(bVal); if (op == "ShowMars" && bOk) Options::setShowMars(bVal); if (op == "ShowJupiter" && bOk) Options::setShowJupiter(bVal); if (op == "ShowSaturn" && bOk) Options::setShowSaturn(bVal); if (op == "ShowUranus" && bOk) Options::setShowUranus(bVal); if (op == "ShowNeptune" && bOk) Options::setShowNeptune(bVal); //if ( op == "ShowPluto" && bOk ) Options::setShowPluto( bVal ); if (op == "ShowAsteroids" && bOk) Options::setShowAsteroids(bVal); if (op == "ShowComets" && bOk) Options::setShowComets(bVal); if (op == "ShowSolarSystem" && bOk) Options::setShowSolarSystem(bVal); if (op == "ShowDeepSky" && bOk) Options::setShowDeepSky(bVal); if (op == "ShowSupernovae" && bOk) Options::setShowSupernovae(bVal); if (op == "ShowStarNames" && bOk) Options::setShowStarNames(bVal); if (op == "ShowStarMagnitudes" && bOk) Options::setShowStarMagnitudes(bVal); if (op == "ShowAsteroidNames" && bOk) Options::setShowAsteroidNames(bVal); if (op == "ShowCometNames" && bOk) Options::setShowCometNames(bVal); if (op == "ShowPlanetNames" && bOk) Options::setShowPlanetNames(bVal); if (op == "ShowPlanetImages" && bOk) Options::setShowPlanetImages(bVal); if (op == "HideOnSlew" && bOk) Options::setHideOnSlew(bVal); if (op == "HideStars" && bOk) Options::setHideStars(bVal); if (op == "HidePlanets" && bOk) Options::setHidePlanets(bVal); if (op == "HideMessier" && bOk) Options::setHideMessier(bVal); if (op == "HideNGC" && bOk) Options::setHideNGC(bVal); if (op == "HideIC" && bOk) Options::setHideIC(bVal); if (op == "HideMilkyWay" && bOk) Options::setHideMilkyWay(bVal); if (op == "HideCNames" && bOk) Options::setHideCNames(bVal); if (op == "HideCLines" && bOk) Options::setHideCLines(bVal); if (op == "HideCBounds" && bOk) Options::setHideCBounds(bVal); if (op == "HideGrids" && bOk) Options::setHideGrids(bVal); if (op == "HideLabels" && bOk) Options::setHideLabels(bVal); if (op == "UseAltAz" && bOk) Options::setUseAltAz(bVal); if (op == "UseRefraction" && bOk) Options::setUseRefraction(bVal); if (op == "UseAutoLabel" && bOk) Options::setUseAutoLabel(bVal); if (op == "UseHoverLabel" && bOk) Options::setUseHoverLabel(bVal); if (op == "UseAutoTrail" && bOk) Options::setUseAutoTrail(bVal); if (op == "UseAnimatedSlewing" && bOk) Options::setUseAnimatedSlewing(bVal); if (op == "FadePlanetTrails" && bOk) Options::setFadePlanetTrails(bVal); if (op == "SlewTimeScale" && dOk) Options::setSlewTimeScale(dVal); if (op == "ZoomFactor" && dOk) Options::setZoomFactor(dVal); // if ( op == "MagLimitDrawStar" && dOk ) Options::setMagLimitDrawStar( dVal ); if (op == "MagLimitDrawDeepSky" && dOk) Options::setMagLimitDrawDeepSky(dVal); if (op == "StarDensity" && dOk) Options::setStarDensity(dVal); // if ( op == "MagLimitDrawStarZoomOut" && dOk ) Options::setMagLimitDrawStarZoomOut( dVal ); if (op == "MagLimitDrawDeepSkyZoomOut" && dOk) Options::setMagLimitDrawDeepSkyZoomOut(dVal); if (op == "StarLabelDensity" && dOk) Options::setStarLabelDensity(dVal); if (op == "MagLimitHideStar" && dOk) Options::setMagLimitHideStar(dVal); if (op == "MagLimitAsteroid" && dOk) Options::setMagLimitAsteroid(dVal); if (op == "AsteroidLabelDensity" && dOk) Options::setAsteroidLabelDensity(dVal); if (op == "MaxRadCometName" && dOk) Options::setMaxRadCometName(dVal); //these three are a "radio group" if (op == "UseLatinConstellationNames" && bOk) { Options::setUseLatinConstellNames(true); Options::setUseLocalConstellNames(false); Options::setUseAbbrevConstellNames(false); } if (op == "UseLocalConstellationNames" && bOk) { Options::setUseLatinConstellNames(false); Options::setUseLocalConstellNames(true); Options::setUseAbbrevConstellNames(false); } if (op == "UseAbbrevConstellationNames" && bOk) { Options::setUseLatinConstellNames(false); Options::setUseLocalConstellNames(false); Options::setUseAbbrevConstellNames(true); } map()->forceUpdate(); } void KStars::setColor(const QString &name, const QString &value) { ColorScheme *cs = data()->colorScheme(); if (cs->hasColorNamed(name)) { cs->setColor(name, value); map()->forceUpdate(); } } void KStars::loadColorScheme(const QString &name) { bool ok = data()->colorScheme()->load(name); //QString filename = data()->colorScheme()->fileName(); if (ok) { //set the application colors for the Night Vision scheme if (Options::darkAppColors()) { //OriginalPalette = QApplication::palette(); QApplication::setPalette(DarkPalette); if (KStars::Instance()->wiView()) KStars::Instance()->wiView()->setNightVisionOn(true); //Note: This uses style sheets to set the dark colors, this is 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 KStars.cpp. If you change it, change it in BOTH places. #ifdef Q_OS_OSX qDebug() << "setting dark stylesheet"; 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) }" ""); qDebug() << "stylesheet set"; #endif } else { if (KStars::Instance()->wiView()) KStars::Instance()->wiView()->setNightVisionOn(false); QApplication::setPalette(OriginalPalette); #ifdef Q_OS_OSX qDebug() << "setting light stylesheet"; qApp->setStyleSheet(""); qDebug() << "stylesheet set"; #endif } #ifdef HAVE_INDI if (KStars::Instance()->ekosManager()) { if (KStars::Instance()->ekosManager()->guideModule()) { KStars::Instance()->ekosManager()->guideModule()->refreshColorScheme(); } } #endif Options::setColorSchemeFile(name); map()->forceUpdate(); } } void KStars::exportImage(const QString &url, int w, int h, bool includeLegend) { ImageExporter *m_ImageExporter = m_KStarsData->imageExporter(); if (w <= 0) w = map()->width(); if (h <= 0) h = map()->height(); QSize size(w, h); m_ImageExporter->includeLegend(includeLegend); m_ImageExporter->setRasterOutputSize(&size); m_ImageExporter->exportImage(url); } QString KStars::getDSSURL(const QString &objectName) { SkyObject *target = data()->objectNamed(objectName); if (!target) { return QString("ERROR"); } else { return KSDssDownloader::getDSSURL(target); } } QString KStars::getDSSURL(double RA_J2000, double Dec_J2000, float width, float height) { dms ra(RA_J2000), dec(Dec_J2000); return KSDssDownloader::getDSSURL(ra, dec, width, height); } QString KStars::getObjectDataXML(const QString &objectName) { SkyObject *target = data()->objectNamed(objectName); if (!target) { return QString(""); } QString output; QXmlStreamWriter stream(&output); stream.setAutoFormatting(true); stream.writeStartDocument(); stream.writeStartElement("object"); stream.writeTextElement("Name", target->name()); stream.writeTextElement("Alt_Name", target->name2()); stream.writeTextElement("Long_Name", target->longname()); stream.writeTextElement("Constellation", KStarsData::Instance()->skyComposite()->constellationBoundary()->constellationName(target)); stream.writeTextElement("RA_Dec_Epoch_JD", QString::number(target->getLastPrecessJD(), 'f', 3)); stream.writeTextElement("RA_HMS", target->ra().toHMSString()); stream.writeTextElement("Dec_DMS", target->dec().toDMSString()); stream.writeTextElement("RA_J2000_HMS", target->ra0().toHMSString()); stream.writeTextElement("Dec_J2000_DMS", target->dec0().toDMSString()); stream.writeTextElement("RA_Degrees", QString::number(target->ra().Degrees())); stream.writeTextElement("Dec_Degrees", QString::number(target->dec().Degrees())); stream.writeTextElement("RA_J2000_Degrees", QString::number(target->ra0().Degrees())); stream.writeTextElement("Dec_J2000_Degrees", QString::number(target->dec0().Degrees())); stream.writeTextElement("Type", target->typeName()); stream.writeTextElement("Magnitude", QString::number(target->mag(), 'g', 2)); stream.writeTextElement("Position_Angle", QString::number(target->pa(), 'g', 3)); StarObject *star = dynamic_cast(target); DeepSkyObject *dso = dynamic_cast(target); if (star) { stream.writeTextElement("Spectral_Type", star->sptype()); stream.writeTextElement("Genetive_Name", star->gname()); stream.writeTextElement("Greek_Letter", star->greekLetter()); stream.writeTextElement("Proper_Motion", QString::number(star->pmMagnitude())); stream.writeTextElement("Proper_Motion_RA", QString::number(star->pmRA())); stream.writeTextElement("Proper_Motion_Dec", QString::number(star->pmDec())); stream.writeTextElement("Parallax_mas", QString::number(star->parallax())); stream.writeTextElement("Distance_pc", QString::number(star->distance())); stream.writeTextElement("Henry_Draper", QString::number(star->getHDIndex())); stream.writeTextElement("BV_Index", QString::number(star->getBVIndex())); } else if (dso) { stream.writeTextElement("Catalog", dso->catalog()); stream.writeTextElement("Major_Axis", QString::number(dso->a())); stream.writeTextElement("Minor_Axis", QString::number(dso->a() * dso->e())); } stream.writeEndElement(); // object stream.writeEndDocument(); return output; } QString KStars::getObjectPositionInfo(const QString &objectName) { Q_ASSERT(data()); const SkyObject *obj = data()->objectNamed(objectName); // make sure we work with a clone if (!obj) { return QString(""); } SkyObject *target = obj->clone(); if (!target) // should not happen { qWarning() << "ERROR: Could not clone SkyObject " << objectName << "!"; return QString(""); } const KSNumbers *updateNum = data()->updateNum(); const KStarsDateTime ut = data()->ut(); const GeoLocation *geo = data()->geo(); QString riseTimeString, setTimeString, transitTimeString; QString riseAzString, setAzString, transitAltString; // Make sure the coordinates of the SkyObject are updated target->updateCoords(updateNum, true, geo->lat(), data()->lst(), true); target->EquatorialToHorizontal(data()->lst(), geo->lat()); // Compute rise, set and transit times and parameters -- Code pulled from DetailDialog QTime riseTime = target->riseSetTime(ut, geo, true); //true = use rise time dms riseAz = target->riseSetTimeAz(ut, geo, true); //true = use rise time QTime transitTime = target->transitTime(ut, geo); dms transitAlt = target->transitAltitude(ut, geo); if (transitTime < riseTime) { transitTime = target->transitTime(ut.addDays(1), geo); transitAlt = target->transitAltitude(ut.addDays(1), geo); } //If set time is before rise time, use set time for tomorrow QTime setTime = target->riseSetTime(ut, geo, false); //false = use set time dms setAz = target->riseSetTimeAz(ut, geo, false); //false = use set time if (setTime < riseTime) { setTime = target->riseSetTime(ut.addDays(1), geo, false); //false = use set time setAz = target->riseSetTimeAz(ut.addDays(1), geo, false); //false = use set time } if (riseTime.isValid()) { riseTimeString = QString().sprintf("%02d:%02d", riseTime.hour(), riseTime.minute()); setTimeString = QString().sprintf("%02d:%02d", setTime.hour(), setTime.minute()); riseAzString = riseAz.toDMSString(true, true); setAzString = setAz.toDMSString(true, true); } else { if (target->alt().Degrees() > 0.0) { riseTimeString = setTimeString = QString("Circumpolar"); } else { riseTimeString = setTimeString = QString("Never Rises"); } riseAzString = setAzString = QString("N/A"); } transitTimeString = QString().sprintf("%02d:%02d", transitTime.hour(), transitTime.minute()); transitAltString = transitAlt.toDMSString(true, true); QString output; QXmlStreamWriter stream(&output); stream.setAutoFormatting(true); stream.writeStartDocument(); stream.writeStartElement("object"); stream.writeTextElement("Name", target->name()); stream.writeTextElement("RA_Dec_Epoch_JD", QString::number(target->getLastPrecessJD(), 'f', 3)); stream.writeTextElement("AltAz_JD", QString::number(data()->ut().djd(), 'f', 3)); stream.writeTextElement("RA_HMS", target->ra().toHMSString(true)); stream.writeTextElement("Dec_DMS", target->dec().toDMSString(true, true)); stream.writeTextElement("RA_J2000_HMS", target->ra0().toHMSString(true)); stream.writeTextElement("Dec_J2000_DMS", target->dec0().toDMSString(true, true)); stream.writeTextElement("RA_Degrees", QString::number(target->ra().Degrees())); stream.writeTextElement("Dec_Degrees", QString::number(target->dec().Degrees())); stream.writeTextElement("RA_J2000_Degrees", QString::number(target->ra0().Degrees())); stream.writeTextElement("Dec_J2000_Degrees", QString::number(target->dec0().Degrees())); stream.writeTextElement("Altitude_DMS", target->alt().toDMSString(true, true)); stream.writeTextElement("Azimuth_DMS", target->az().toDMSString(true, true)); stream.writeTextElement("Altitude_Degrees", QString::number(target->alt().Degrees())); stream.writeTextElement("Azimuth_Degrees", QString::number(target->az().Degrees())); stream.writeTextElement("Rise", riseTimeString); stream.writeTextElement("Rise_Az_DMS", riseAzString); stream.writeTextElement("Set", setTimeString); stream.writeTextElement("Set_Az_DMS", setAzString); stream.writeTextElement("Transit", transitTimeString); stream.writeTextElement("Transit_Alt_DMS", transitAltString); stream.writeTextElement("Time_Zone_Offset", QString().sprintf("%02.2f", geo->TZ())); stream.writeEndElement(); // object stream.writeEndDocument(); return output; } void KStars::renderEyepieceView(const QString &objectName, const QString &destPathChart, const double fovWidth, const double fovHeight, const double rotation, const double scale, const bool flip, const bool invert, QString imagePath, const QString &destPathImage, const bool overlay, const bool invertColors) { const SkyObject *obj = data()->objectNamed(objectName); if (!obj) { qCWarning(KSTARS) << "Object named " << objectName << " was not found!"; return; } SkyObject *target = obj->clone(); const KSNumbers *updateNum = data()->updateNum(); const KStarsDateTime ut = data()->ut(); const GeoLocation *geo = data()->geo(); QPixmap *renderChart = new QPixmap(); - QPixmap *renderImage = 0; + QPixmap *renderImage = nullptr; QTemporaryFile tempFile; if (overlay || (!destPathImage.isEmpty())) { if (!QFile::exists(imagePath)) { // We must download a DSS image tempFile.open(); QEventLoop loop; std::function slot = [&loop](bool unused) { Q_UNUSED(unused); loop.quit(); }; new KSDssDownloader(target, tempFile.fileName(), slot, this); qDebug() << "DSS download requested. Waiting for download to complete..."; loop.exec(); // wait for download to complete imagePath = tempFile.fileName(); } if (QFile::exists(imagePath)) // required because DSS Download may fail renderImage = new QPixmap(); } // Make sure the coordinates of the SkyObject are updated target->updateCoords(updateNum, true, geo->lat(), data()->lst(), true); target->EquatorialToHorizontal(data()->lst(), geo->lat()); EyepieceField::renderEyepieceView(target, renderChart, fovWidth, fovHeight, rotation, scale, flip, invert, imagePath, renderImage, overlay, invertColors); renderChart->save(destPathChart); delete renderChart; if (renderImage) { renderImage->save(destPathImage); delete renderImage; } } QString KStars::getObservingWishListObjectNames() { QString output; for (auto &object : KStarsData::Instance()->observingList()->obsList()) { output.append(object->name() + '\n'); } return output; } QString KStars::getObservingSessionPlanObjectNames() { QString output; for (auto &object : KStarsData::Instance()->observingList()->sessionList()) { output.append(object->name() + '\n'); } return output; } void KStars::setApproxFOV(double FOV_Degrees) { zoom(map()->width() / (FOV_Degrees * dms::DegToRad)); } QString KStars::getSkyMapDimensions() { return (QString::number(map()->width()) + 'x' + QString::number(map()->height())); } void KStars::printImage(bool usePrintDialog, bool useChartColors) { //QPRINTER_FOR_NOW // KPrinter printer( true, QPrinter::HighResolution ); QPrinter printer(QPrinter::HighResolution); printer.setFullPage(false); //Set up the printer (either with the Print Dialog, //or using the default settings) bool ok(false); if (usePrintDialog) { //QPRINTER_FOR_NOW // ok = printer.setup( this, i18n("Print Sky") ); //QPrintDialog *dialog = KdePrint::createPrintDialog(&printer, this); QPrintDialog *dialog = new QPrintDialog(&printer, this); dialog->setWindowTitle(i18n("Print Sky")); if (dialog->exec() == QDialog::Accepted) ok = true; delete dialog; } else { //QPRINTER_FOR_NOW // ok = printer.autoConfigure(); ok = true; } if (ok) { QApplication::setOverrideCursor(Qt::WaitCursor); //Save current ColorScheme file name and switch to Star Chart //scheme (if requested) QString schemeName = data()->colorScheme()->fileName(); if (useChartColors) { loadColorScheme("chart.colors"); } map()->setupProjector(); map()->exportSkyImage(&printer, true); //Restore old color scheme if necessary //(if printing was aborted, the ColorScheme is still restored) if (useChartColors) { loadColorScheme(schemeName); map()->forceUpdate(); } QApplication::restoreOverrideCursor(); } } bool KStars::openFITS(const QString &imageURL) { QUrl url(imageURL); return openFITS(url); } bool KStars::openFITS(const QUrl &imageURL) { #ifndef HAVE_CFITSIO qWarning() << "KStars does not support loading FITS. Please recompile KStars with FITS support."; return false; #else FITSViewer *fv = nullptr; if (Options::singleWindowOpenedFITS()) fv = genericFITSViewer(); else { fv = new FITSViewer((Options::independentWindowFITS()) ? nullptr : this); KStars::Instance()->getFITSViewersList().append(fv); } // Error opening file if (fv->addFITS(&imageURL) == -2) { delete (fv); return false; } else { fv->show(); return true; } #endif } diff --git a/kstars/oal/execute.cpp b/kstars/oal/execute.cpp index 0bd80ff0e..da4b23349 100644 --- a/kstars/oal/execute.cpp +++ b/kstars/oal/execute.cpp @@ -1,483 +1,483 @@ /*************************************************************************** execute.cpp - description ------------------- begin : Friday July 21, 2009 copyright : (C) 2009 by Prakash Mohan email : prakash.mohan@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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 "oal/execute.h" #include "kstars.h" #include "kstarsdata.h" #include "observinglist.h" #include "dialogs/finddialog.h" #include "dialogs/locationdialog.h" #include "oal/observeradd.h" #include "skycomponents/skymapcomposite.h" #include "skyobjects/starobject.h" #include Execute::Execute() { QWidget *w = new QWidget; ui.setupUi(w); #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(w); setLayout(mainLayout); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); QPushButton *execB = new QPushButton(i18n("End Session")); QPushButton *addObs = new QPushButton(i18n("Manage Observers")); execB->setToolTip(i18n("Save and End the current session")); buttonBox->addButton(execB, QDialogButtonBox::ActionRole); buttonBox->addButton(addObs, QDialogButtonBox::ActionRole); connect(execB, SIGNAL(clicked()), this, SLOT(slotEndSession())); connect(addObs, SIGNAL(clicked()), this, SLOT(slotObserverAdd())); setWindowTitle(i18n("Execute Session")); //initialize the global logObject logObject = KStarsData::Instance()->logObject(); //initialize the lists and parameters init(); ui.Target->hide(); ui.AddObject->hide(); ui.RemoveObject->hide(); ui.NextButton->hide(); ui.NextButton->setEnabled(false); ui.Slew->setEnabled(false); //make connections connect(ui.NextButton, SIGNAL(clicked()), this, SLOT(slotNext())); connect(ui.Slew, SIGNAL(clicked()), this, SLOT(slotSlew())); connect(ui.Location, SIGNAL(clicked()), this, SLOT(slotLocation())); connect(ui.Target, SIGNAL(currentTextChanged(QString)), this, SLOT(slotSetTarget(QString))); connect(ui.SessionURL, SIGNAL(leftClickedUrl()), this, SLOT(slotShowSession())); connect(ui.ObservationsURL, SIGNAL(leftClickedUrl()), this, SLOT(slotShowTargets())); connect(ui.AddObject, SIGNAL(leftClickedUrl()), this, SLOT(slotAddObject())); connect(ui.RemoveObject, SIGNAL(leftClickedUrl()), this, SLOT(slotRemoveObject())); } void Execute::init() { //initialize geo to current location of the ObservingList geo = KStarsData::Instance()->geo(); ui.Location->setText(geo->fullName()); // JM: Aren't we suppose to take KStars time? The one returned by the OL is the time of the LAST object // in the list which doesn't make sense. /* //set the date time to the dateTime from the OL ui.Begin->setDateTime( ks->observingList()->dateTime() ); */ ui.Begin->setDateTime(KStarsData::Instance()->geo()->UTtoLT(KStarsData::Instance()->clock()->utc())); KStarsData::Instance()->logObject()->readAll(); //load Targets loadTargets(); //load Equipment loadEquipment(); //load Observers loadObservers(); if (logObject->scopeList()->isEmpty() || logObject->observerList()->isEmpty()) { ui.hintLabel->show(); } else { ui.hintLabel->hide(); } //set Current Items loadCurrentItems(); } void Execute::loadCurrentItems() { //Set the current target, equipments and observer if (currentTarget) ui.Target->setCurrentRow(findIndexOfTarget(currentTarget->name()), QItemSelectionModel::SelectCurrent); else ui.Target->setCurrentRow(0, QItemSelectionModel::SelectCurrent); if (currentObserver) ui.Observer->setCurrentIndex(ui.Observer->findText(currentObserver->name() + ' ' + currentObserver->surname())); if (currentScope) ui.Scope->setCurrentIndex(ui.Scope->findText(currentScope->name())); if (currentEyepiece) ui.Eyepiece->setCurrentIndex(ui.Eyepiece->findText(currentEyepiece->name())); if (currentLens) ui.Lens->setCurrentIndex(ui.Lens->findText(currentLens->name())); if (currentFilter) ui.Filter->setCurrentIndex(ui.Filter->findText(currentFilter->name())); } int Execute::findIndexOfTarget(QString name) { for (int i = 0; i < ui.Target->count(); i++) if (ui.Target->item(i)->text() == name) return i; return -1; } void Execute::slotNext() { switch (ui.stackedWidget->currentIndex()) { case 0: { saveSession(); break; } case 1: { addTargetNotes(); break; } case 2: { addObservation(); ui.stackedWidget->setCurrentIndex(1); ui.NextButton->setText(i18n("Next Page >")); QString prevTarget = currentTarget->name(); loadTargets(); ui.Target->setCurrentRow(findIndexOfTarget(prevTarget), QItemSelectionModel::SelectCurrent); selectNextTarget(); break; } } } bool Execute::saveSession() { OAL::Site *site = logObject->findSiteByName(geo->fullName()); if (!site) { while (logObject->findSiteById(i18n("site_") + QString::number(nextSite))) nextSite++; site = new OAL::Site(geo, i18n("site_") + QString::number(nextSite++)); logObject->siteList()->append(site); } if (currentSession) { currentSession->setSession(currentSession->id(), site->id(), KStarsDateTime(ui.Begin->dateTime()), KStarsDateTime(ui.Begin->dateTime()), ui.Weather->toPlainText(), ui.Equipment->toPlainText(), ui.Comment->toPlainText(), ui.Language->text()); } else { while (logObject->findSessionByName(i18n("session_") + QString::number(nextSession))) nextSession++; currentSession = new OAL::Session(i18n("session_") + QString::number(nextSession++), site->id(), KStarsDateTime(ui.Begin->dateTime()), KStarsDateTime(ui.Begin->dateTime()), ui.Weather->toPlainText(), ui.Equipment->toPlainText(), ui.Comment->toPlainText(), ui.Language->text()); logObject->sessionList()->append(currentSession); } ui.stackedWidget->setCurrentIndex(1); //Move to the next page return true; } void Execute::slotLocation() { QPointer ld = new LocationDialog(this); if (ld->exec() == QDialog::Accepted) { geo = ld->selectedCity(); ui.Location->setText(geo->fullName()); } delete ld; } void Execute::loadTargets() { ui.Target->clear(); sortTargetList(); for (auto &o : KStarsData::Instance()->observingList()->sessionList()) { ui.Target->addItem(getObjectName(o.data(), false)); } } void Execute::loadEquipment() { ui.Scope->clear(); ui.Eyepiece->clear(); ui.Lens->clear(); ui.Filter->clear(); foreach (OAL::Scope *s, *(logObject->scopeList())) ui.Scope->addItem(s->name()); foreach (OAL::Eyepiece *e, *(logObject->eyepieceList())) ui.Eyepiece->addItem(e->name()); foreach (OAL::Lens *l, *(logObject->lensList())) ui.Lens->addItem(l->name()); foreach (OAL::Filter *f, *(logObject->filterList())) ui.Filter->addItem(f->name()); } void Execute::loadObservers() { ui.Observer->clear(); foreach (OAL::Observer *o, *(logObject->observerList())) ui.Observer->addItem(o->name() + ' ' + o->surname()); } void Execute::sortTargetList() { auto timeLessThan = [](QSharedPointer o1, QSharedPointer o2) { QTime t1 = KStarsData::Instance()->observingList()->scheduledTime(o1.data()); QTime t2 = KStarsData::Instance()->observingList()->scheduledTime(o2.data()); if (t1 < QTime(12, 0, 0)) t1.setHMS(t1.hour() + 12, t1.minute(), t1.second()); else t1.setHMS(t1.hour() - 12, t1.minute(), t1.second()); if (t2 < QTime(12, 0, 0)) t2.setHMS(t2.hour() + 12, t2.minute(), t2.second()); else t2.setHMS(t2.hour() - 12, t2.minute(), t2.second()); return (t1 < t2); }; qSort(KStarsData::Instance()->observingList()->sessionList().begin(), KStarsData::Instance()->observingList()->sessionList().end(), timeLessThan); } void Execute::addTargetNotes() { if (!ui.Target->count()) return; SkyObject *o = KStarsData::Instance()->observingList()->findObjectByName(ui.Target->currentItem()->text()); if (o) { currentTarget = o; o->setNotes(ui.Notes->toPlainText()); ui.Notes->clear(); loadObservationTab(); } } void Execute::loadObservationTab() { ui.Time->setTime(KStarsDateTime::currentDateTime().time()); ui.stackedWidget->setCurrentIndex(2); ui.NextButton->setText(i18n("Next Target >")); } bool Execute::addObservation() { slotSetCurrentObjects(); while (logObject->findObservationByName(i18n("observation_") + QString::number(nextObservation))) nextObservation++; KStarsDateTime dt = currentSession->begin(); dt.setTime(ui.Time->time()); OAL::Observation *o = new OAL::Observation( i18n("observation_") + QString::number(nextObservation++), currentObserver, currentSession, currentTarget, dt, ui.FaintestStar->value(), ui.Seeing->value(), currentScope, currentEyepiece, currentLens, currentFilter, ui.Description->toPlainText(), ui.Language->text()); logObject->observationList()->append(o); ui.Description->clear(); return true; } void Execute::slotEndSession() { if (currentSession) { currentSession->setSession(currentSession->id(), currentSession->site(), KStarsDateTime(ui.Begin->dateTime()), KStarsDateTime::currentDateTime(), ui.Weather->toPlainText(), ui.Equipment->toPlainText(), ui.Comment->toPlainText(), ui.Language->text()); - QUrl fileURL = QFileDialog::getSaveFileUrl(0, i18n("Save Session"), QUrl(QDir::homePath()), "*.xml"); + QUrl fileURL = QFileDialog::getSaveFileUrl(nullptr, i18n("Save Session"), QUrl(QDir::homePath()), "*.xml"); if (fileURL.isEmpty()) { // Cancel return; } if (fileURL.isValid()) { QFile f(fileURL.toLocalFile()); if (!f.open(QIODevice::WriteOnly)) { QString message = i18n("Could not open file %1", f.fileName()); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); return; } QTextStream ostream(&f); ostream << logObject->writeLog(false); f.close(); } } hide(); ui.stackedWidget->setCurrentIndex(0); logObject->observationList()->clear(); logObject->sessionList()->clear(); delete currentSession; currentTarget = nullptr; currentSession = nullptr; } void Execute::slotObserverAdd() { QPointer m_observerAdd = new ObserverAdd(); m_observerAdd->exec(); delete m_observerAdd; } void Execute::slotSetTarget(const QString &name) { currentTarget = KStarsData::Instance()->observingList()->findObjectByName(name); if (!currentTarget) { ui.NextButton->setEnabled(false); ui.Slew->setEnabled(false); return; } else { ui.NextButton->setEnabled(true); ui.Slew->setEnabled(true); KStarsData::Instance()->observingList()->selectObject(currentTarget); KStarsData::Instance()->observingList()->slotCenterObject(); QString smag = "--"; if (-30.0 < currentTarget->mag() && currentTarget->mag() < 90.0) smag = QString::number(currentTarget->mag(), 'g', 2); // The lower limit to avoid display of unrealistic comet magnitudes ui.Mag->setText(smag); ui.Type->setText(currentTarget->typeName()); ui.SchTime->setText( KStarsData::Instance()->observingList()->scheduledTime(currentTarget).toString("h:mm:ss AP")); SkyPoint p = currentTarget->recomputeCoords(KStarsDateTime::currentDateTime(), geo); dms lst(geo->GSTtoLST(KStarsDateTime::currentDateTime().gst())); p.EquatorialToHorizontal(&lst, geo->lat()); ui.RA->setText(p.ra().toHMSString()); ui.Dec->setText(p.dec().toDMSString()); ui.Alt->setText(p.alt().toDMSString()); ui.Az->setText(p.az().toDMSString()); ui.Notes->setText(currentTarget->notes()); } } void Execute::slotSlew() { KStarsData::Instance()->observingList()->slotSlewToObject(); } void Execute::selectNextTarget() { int i = findIndexOfTarget(currentTarget->name()) + 1; if (i < ui.Target->count()) { ui.Target->selectionModel()->clear(); ui.Target->setCurrentRow(i, QItemSelectionModel::SelectCurrent); } } void Execute::slotSetCurrentObjects() { currentScope = logObject->findScopeByName(ui.Scope->currentText()); currentEyepiece = logObject->findEyepieceByName(ui.Eyepiece->currentText()); currentLens = logObject->findLensByName(ui.Lens->currentText()); currentFilter = logObject->findFilterByName(ui.Filter->currentText()); currentObserver = logObject->findObserverByName(ui.Observer->currentText()); } void Execute::slotShowSession() { ui.Target->hide(); ui.stackedWidget->setCurrentIndex(0); ui.NextButton->hide(); ui.AddObject->hide(); ui.RemoveObject->hide(); } void Execute::slotShowTargets() { if (saveSession()) { ui.Target->show(); ui.AddObject->show(); ui.RemoveObject->show(); ui.stackedWidget->setCurrentIndex(1); ui.NextButton->show(); ui.NextButton->setText(i18n("Next Page >")); } } void Execute::slotAddObject() { QPointer fd = new FindDialog(KStars::Instance()); if (fd->exec() == QDialog::Accepted) { SkyObject *o = fd->targetObject(); - if (o != 0) + if (o != nullptr) { KStarsData::Instance()->observingList()->slotAddObject(o, true); init(); } } delete fd; } void Execute::slotRemoveObject() { QModelIndex i = ui.Target->currentIndex(); SkyObject *obj = nullptr; if (i.isValid()) { QString ObjName = i.data().toString(); obj = KStarsData::Instance()->skyComposite()->findByName(ObjName); } if (obj != nullptr) { KStarsData::Instance()->observingList()->slotRemoveObject(obj, true); loadTargets(); } } QString Execute::getObjectName(const SkyObject *o, bool translated) { QString finalObjectName; if (o->name() == "star") { StarObject *s = (StarObject *)o; // JM: Enable HD Index stars to be added to the observing list. if (s->getHDIndex() != 0) finalObjectName = QString("HD %1").arg(QString::number(s->getHDIndex())); } else finalObjectName = translated ? o->translatedName() : o->name(); return finalObjectName; } diff --git a/kstars/oal/oal.h b/kstars/oal/oal.h index 0bca812f1..6eef88587 100644 --- a/kstars/oal/oal.h +++ b/kstars/oal/oal.h @@ -1,63 +1,63 @@ /*************************************************************************** oal.h - description ------------------- begin : Friday June 19, 2009 copyright : (C) 2009 by Prakash Mohan email : prakash.mohan@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef OAL_H_ #define OAL_H_ #include #include #ifndef KSTARS_LITE #include #endif #include #include /** * @namespace OAL * * Open Astronomy Log (OAL) is a free and open XML schema definition for all kinds of astronomical observations. * KStars supports this schema and enables an observer to share observations with other observers or move observations among software products. * * The Schema was developed by the German "Fachgruppe für Computerastronomie" (section for computerastronomy) which is a subsection of Germany's largest * astronomy union (VDS - Vereinigung der Sternfreunde e.V.) */ namespace OAL { class Log; class Observer; class Observation; class Equipment; class Eyepiece; class Scope; class Filter; class Imager; class Site; class Session; class Target; class Lens; inline int warningOverwrite(QString message) { #ifndef KSTARS_LITE - return KMessageBox::warningYesNo(0, message, xi18n("Overwrite"), KStandardGuiItem::overwrite(), + return KMessageBox::warningYesNo(nullptr, message, xi18n("Overwrite"), KStandardGuiItem::overwrite(), KStandardGuiItem::cancel()); #else return 0; #endif } } #endif diff --git a/kstars/oal/observeradd.cpp b/kstars/oal/observeradd.cpp index 5ac9f2403..db7f7b6fb 100644 --- a/kstars/oal/observeradd.cpp +++ b/kstars/oal/observeradd.cpp @@ -1,125 +1,125 @@ /*************************************************************************** observeradd.cpp - description ------------------- begin : Sunday July 26, 2009 copyright : (C) 2009 by Prakash Mohan email : prakash.mohan@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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 "observeradd.h" #include "kstarsdata.h" #include ObserverAdd::ObserverAdd() { // Setting up the widget from the .ui file and adding it to the QDialog QWidget *widget = new QWidget; ui.setupUi(widget); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(widget); setLayout(mainLayout); setWindowTitle(i18n("Manage Observers")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); ui.AddObserverB->setIcon(QIcon::fromTheme("list-add", QIcon(":/icons/breeze/default/list-add.svg"))); ui.RemoveObserverB->setIcon(QIcon::fromTheme("list-remove", QIcon(":/icons/breeze/default/list-remove.svg"))); // Load the observers list from the file loadObservers(); QSqlDatabase db = KStarsData::Instance()->userdb()->GetDatabase(); - QSqlTableModel *users = new QSqlTableModel(0, db); + QSqlTableModel *users = new QSqlTableModel(nullptr, db); users->setTable("user"); users->select(); ui.tableView->setModel(users); ui.tableView->setColumnHidden(0, true); ui.tableView->horizontalHeader()->resizeContentsPrecision(); ui.tableView->viewport()->update(); ui.AddObserverB->setEnabled(false); ui.RemoveObserverB->setEnabled(false); ui.tableView->setSelectionMode(QAbstractItemView::SingleSelection); // Make connections connect(ui.AddObserverB, SIGNAL(clicked()), this, SLOT(slotAddObserver())); connect(ui.RemoveObserverB, SIGNAL(clicked()), this, SLOT(slotRemoveObserver())); connect(ui.Name, SIGNAL(textChanged(QString)), this, SLOT(checkObserverInfo())); connect(ui.Surname, SIGNAL(textChanged(QString)), this, SLOT(checkObserverInfo())); //connect (ui.tableView->verticalHeader(),SIGNAL(sectionClicked(int)),this,SLOT(checkTableInfo())); connect(ui.tableView, SIGNAL(clicked(QModelIndex)), this, SLOT(auxSlot())); } void ObserverAdd::auxSlot() { ui.RemoveObserverB->setEnabled(true); } void ObserverAdd::checkObserverInfo() { if (ui.Name->text().isEmpty() || ui.Surname->text().isEmpty()) ui.AddObserverB->setEnabled(false); else ui.AddObserverB->setEnabled(true); } void ObserverAdd::slotUpdateModel() { QSqlDatabase db = KStarsData::Instance()->userdb()->GetDatabase(); - QSqlTableModel *users = new QSqlTableModel(0, db); + QSqlTableModel *users = new QSqlTableModel(nullptr, db); users->setTable("user"); users->select(); ui.tableView->setModel(users); ui.tableView->setColumnHidden(0, true); ui.tableView->horizontalHeader()->resizeContentsPrecision(); ui.tableView->viewport()->update(); } void ObserverAdd::slotRemoveObserver() { QModelIndex index = ui.tableView->currentIndex(); int nr = index.row(); QString s = ui.tableView->model()->data(ui.tableView->model()->index(nr, 0)).toString(); KStarsData::Instance()->userdb()->DeleteObserver(s); ui.RemoveObserverB->setEnabled(false); slotUpdateModel(); } void ObserverAdd::slotAddObserver() { if (KStarsData::Instance()->userdb()->FindObserver(ui.Name->text(), ui.Surname->text())) { if (OAL::warningOverwrite( i18n("Another Observer already exists with the given Name and Surname, Overwrite?")) == KMessageBox::No) return; } KStarsData::Instance()->userdb()->AddObserver(ui.Name->text(), ui.Surname->text(), ui.Contact->text()); //Reload observers into OAL::m_observers loadObservers(); // Reset the UI for a fresh addition ui.Name->clear(); ui.Surname->clear(); ui.Contact->clear(); slotUpdateModel(); } void ObserverAdd::loadObservers() { KStarsData::Instance()->logObject()->readObservers(); } diff --git a/kstars/options/opscatalog.cpp b/kstars/options/opscatalog.cpp index 40bd01b2a..0888b77e4 100644 --- a/kstars/options/opscatalog.cpp +++ b/kstars/options/opscatalog.cpp @@ -1,394 +1,394 @@ /*************************************************************************** opscatalog.cpp - K Desktop Planetarium ------------------- begin : Sun Feb 29 2004 copyright : (C) 2004 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 "opscatalog.h" #include #include #include #include #include #include #include "Options.h" #include "kstars.h" #include "kstarsdata.h" #include "skymap.h" #include "dialogs/addcatdialog.h" #include "widgets/magnitudespinbox.h" #include "skycomponents/catalogcomponent.h" #include "skycomponents/skymapcomposite.h" OpsCatalog::OpsCatalog() : QFrame(KStars::Instance()) { setupUi(this); //Get a pointer to the KConfigDialog m_ConfigDialog = KConfigDialog::exists("settings"); //Populate CatalogList populateInbuiltCatalogs(); m_ShowMessier = Options::showMessier(); m_ShowMessImages = Options::showMessierImages(); m_ShowNGC = Options::showNGC(); m_ShowIC = Options::showIC(); // kcfg_MagLimitDrawStar->setValue( Options::magLimitDrawStar() ); kcfg_StarDensity->setValue(Options::starDensity()); // kcfg_MagLimitDrawStarZoomOut->setValue( Options::magLimitDrawStarZoomOut() ); // m_MagLimitDrawStar = kcfg_MagLimitDrawStar->value(); m_StarDensity = kcfg_StarDensity->value(); // m_MagLimitDrawStarZoomOut = kcfg_MagLimitDrawStarZoomOut->value(); // kcfg_MagLimitDrawStar->setMinimum( Options::magLimitDrawStarZoomOut() ); // kcfg_MagLimitDrawStarZoomOut->setMaximum( 12.0 ); kcfg_MagLimitDrawDeepSky->setMaximum(16.0); kcfg_MagLimitDrawDeepSkyZoomOut->setMaximum(16.0); //disable star-related widgets if not showing stars if (!kcfg_ShowStars->isChecked()) slotStarWidgets(false); //Add custom catalogs, if necessary /* * 1) Get the list from DB and add it as unchecked * 2) If the showCatalogNames list has any of the items, check it */ m_CustomCatalogFile = KStars::Instance()->data()->catalogdb()->Catalogs(); m_CheckedCatalogNames = Options::showCatalogNames(); populateCustomCatalogs(); connect(CatalogList, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(updateCustomCatalogs())); connect(CatalogList, SIGNAL(itemSelectionChanged()), this, SLOT(selectCatalog())); connect(AddCatalog, SIGNAL(clicked()), this, SLOT(slotAddCatalog())); connect(LoadCatalog, SIGNAL(clicked()), this, SLOT(slotLoadCatalog())); connect(RemoveCatalog, SIGNAL(clicked()), this, SLOT(slotRemoveCatalog())); /* connect( kcfg_MagLimitDrawStar, SIGNAL( valueChanged(double) ), SLOT( slotSetDrawStarMagnitude(double) ) ); connect( kcfg_MagLimitDrawStarZoomOut, SIGNAL( valueChanged(double) ), SLOT( slotSetDrawStarZoomOutMagnitude(double) ) ); */ connect(kcfg_ShowStars, SIGNAL(toggled(bool)), SLOT(slotStarWidgets(bool))); connect(kcfg_ShowDeepSky, SIGNAL(toggled(bool)), SLOT(slotDeepSkyWidgets(bool))); connect(kcfg_ShowDeepSkyNames, SIGNAL(toggled(bool)), kcfg_DeepSkyLongLabels, SLOT(setEnabled(bool))); connect(m_ConfigDialog->button(QDialogButtonBox::Apply), SIGNAL(clicked()), SLOT(slotApply())); connect(m_ConfigDialog->button(QDialogButtonBox::Ok), SIGNAL(clicked()), SLOT(slotApply())); connect(m_ConfigDialog->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), SLOT(slotCancel())); // Keep track of changes connect(CatalogList, &QListWidget::itemChanged, this, [&]() { isDirty = true; }); connect(catalogButtonGroup, static_cast(&QButtonGroup::buttonPressed), this, [&]() { isDirty = true; }); isDirty = false; } //empty destructor OpsCatalog::~OpsCatalog() { } void OpsCatalog::updateCustomCatalogs() { m_ShowMessier = showMessier->checkState(); m_ShowMessImages = showMessImages->checkState(); m_ShowNGC = showNGC->checkState(); m_ShowIC = showIC->checkState(); int limit = m_CustomCatalogFile->size(); for (int i = 0; i < limit; ++i) { QString name = m_CustomCatalogFile->at(i); QList l = CatalogList->findItems(name, Qt::MatchExactly); /* * Options::CatalogNames contains the list of those custom catalog * names which are to be checked. * For every checked item, we check if the option CatalogNames has * the name. If not, we add it. * For every unchecked item, we check if the option CatalogNames does * not contain the name. If it does, we remove it. */ if (l.count() == 0) continue; // skip the name if no match found if (l[0]->checkState() == Qt::Checked) { if (!m_CheckedCatalogNames.contains(name)) { m_CheckedCatalogNames.append(name); //isDirty = true; } } else if (l[0]->checkState() == Qt::Unchecked) { if (m_CheckedCatalogNames.contains(name)) { m_CheckedCatalogNames.removeAll(name); //isDirty = true; } } } m_ConfigDialog->button(QDialogButtonBox::Apply)->setEnabled(false); } void OpsCatalog::selectCatalog() { //If selected item is a custom catalog, enable the remove button (otherwise, disable it) RemoveCatalog->setEnabled(false); if (!CatalogList->currentItem()) return; //isDirty = true; foreach (SkyComponent *sc, KStars::Instance()->data()->skyComposite()->customCatalogs()) { CatalogComponent *cc = (CatalogComponent *)sc; if (CatalogList->currentItem()->text() == cc->name()) { RemoveCatalog->setEnabled(true); break; } } } void OpsCatalog::slotAddCatalog() { QPointer ac = new AddCatDialog(KStars::Instance()); if (ac->exec() == QDialog::Accepted) { KStars::Instance()->data()->catalogdb()->AddCatalogContents(ac->filename()); refreshCatalogList(); isDirty = true; } delete ac; } void OpsCatalog::slotLoadCatalog() { //Get the filename from the user QString filename = QFileDialog::getOpenFileName(KStars::Instance(), QString(), QDir::homePath(), "*"); if (!filename.isEmpty()) { KStars::Instance()->data()->catalogdb()->AddCatalogContents(filename); isDirty = true; refreshCatalogList(); } m_ConfigDialog->button(QDialogButtonBox::Apply)->setEnabled(false); } void OpsCatalog::refreshCatalogList() { KStars::Instance()->data()->catalogdb()->Catalogs(); populateCustomCatalogs(); } void OpsCatalog::slotRemoveCatalog() { if (KMessageBox::warningYesNo( - 0, i18n("The selected database will be removed. This action cannot be reversed! Delete Catalog?"), + nullptr, i18n("The selected database will be removed. This action cannot be reversed! Delete Catalog?"), i18n("Delete Catalog?")) == KMessageBox::No) { - KMessageBox::information(0, "Catalog deletion cancelled."); + KMessageBox::information(nullptr, "Catalog deletion cancelled."); return; } isDirty = true; //Ask DB to remove catalog KStars::Instance()->data()->catalogdb()->RemoveCatalog(CatalogList->currentItem()->text()); // Remove from Options if it exists in it (i.e. was marked as visible) // This does not remove it from the database or from the widget in Options QList checkedlist = Options::showCatalogNames(); if (checkedlist.contains(CatalogList->currentItem()->text())) { checkedlist.removeAll(CatalogList->currentItem()->text()); Options::setShowCatalogNames(checkedlist); } //Remove entry in the QListView QListWidgetItem *todelete = CatalogList->takeItem(CatalogList->row(CatalogList->currentItem())); delete todelete; refreshCatalogList(); m_ConfigDialog->button(QDialogButtonBox::Apply)->setEnabled(false); KStars::Instance()->data()->skyComposite()->reloadDeepSky(); } /* void OpsCatalog::slotSetDrawStarMagnitude(double newValue) { m_MagLimitDrawStar = newValue; kcfg_MagLimitDrawStarZoomOut->setMaximum( newValue ); m_ConfigDialog->enableButtonApply( true ); } void OpsCatalog::slotSetDrawStarZoomOutMagnitude(double newValue) { m_MagLimitDrawStarZoomOut = newValue; kcfg_MagLimitDrawStar->setMinimum( newValue ); m_ConfigDialog->enableButtonApply( true ); } */ void OpsCatalog::slotApply() { if (isDirty == false) return; isDirty = false; refreshCatalogList(); Options::setStarDensity(kcfg_StarDensity->value()); // Options::setMagLimitDrawStarZoomOut( kcfg_MagLimitDrawStarZoomOut->value() ); //FIXME: need to add the ShowDeepSky meta-option to the config dialog! //For now, I'll set showDeepSky to true if any catalog options changed if (m_ShowMessier != Options::showMessier() || m_ShowMessImages != Options::showMessierImages() || m_ShowNGC != Options::showNGC() || m_ShowIC != Options::showIC()) { Options::setShowDeepSky(true); } updateCustomCatalogs(); KStars::Instance()->data()->skyComposite()->reloadDeepSky(); // update time for all objects because they might be not initialized // it's needed when using horizontal coordinates KStars::Instance()->data()->setFullTimeUpdate(); KStars::Instance()->updateTime(); KStars::Instance()->map()->forceUpdate(); Options::setShowCatalogNames(m_CheckedCatalogNames); Options::setShowMessier(m_ShowMessier); Options::setShowMessierImages(m_ShowMessImages); Options::setShowNGC(m_ShowNGC); Options::setShowIC(m_ShowIC); } void OpsCatalog::slotCancel() { //Revert all local option placeholders to the values in the global config object // m_MagLimitDrawStar = Options::magLimitDrawStar(); m_StarDensity = Options::starDensity(); // m_MagLimitDrawStarZoomOut = Options::magLimitDrawStarZoomOut(); m_ShowMessier = Options::showMessier(); m_ShowMessImages = Options::showMessierImages(); m_ShowNGC = Options::showNGC(); m_ShowIC = Options::showIC(); m_ShowCustomCatalog = Options::showCatalog(); } void OpsCatalog::slotStarWidgets(bool on) { // LabelMagStars->setEnabled(on); LabelStarDensity->setEnabled(on); // LabelMagStarsZoomOut->setEnabled(on); LabelDensity->setEnabled(on); // LabelMag1->setEnabled(on); // LabelMag2->setEnabled(on); // kcfg_MagLimitDrawStar->setEnabled(on); kcfg_StarDensity->setEnabled(on); LabelStarDensity->setEnabled(on); // kcfg_MagLimitDrawStarZoomOut->setEnabled(on); kcfg_StarLabelDensity->setEnabled(on); kcfg_ShowStarNames->setEnabled(on); kcfg_ShowStarMagnitudes->setEnabled(on); } void OpsCatalog::slotDeepSkyWidgets(bool on) { CatalogList->setEnabled(on); AddCatalog->setEnabled(on); LoadCatalog->setEnabled(on); LabelMagDeepSky->setEnabled(on); LabelMagDeepSkyZoomOut->setEnabled(on); kcfg_MagLimitDrawDeepSky->setEnabled(on); kcfg_MagLimitDrawDeepSkyZoomOut->setEnabled(on); kcfg_ShowDeepSkyNames->setEnabled(on); kcfg_ShowDeepSkyMagnitudes->setEnabled(on); kcfg_DeepSkyLabelDensity->setEnabled(on); kcfg_DeepSkyLongLabels->setEnabled(on); LabelMag3->setEnabled(on); LabelMag4->setEnabled(on); if (on) { //Enable RemoveCatalog if the selected catalog is custom selectCatalog(); } else { RemoveCatalog->setEnabled(on); } } void OpsCatalog::populateInbuiltCatalogs() { showIC = new QListWidgetItem(i18n("Index Catalog (IC)"), CatalogList); showIC->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); showIC->setCheckState(Options::showIC() ? Qt::Checked : Qt::Unchecked); showNGC = new QListWidgetItem(i18n("New General Catalog (NGC)"), CatalogList); showNGC->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); showNGC->setCheckState(Options::showNGC() ? Qt::Checked : Qt::Unchecked); showMessImages = new QListWidgetItem(i18n("Messier Catalog (images)"), CatalogList); showMessImages->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); showMessImages->setCheckState(Options::showMessierImages() ? Qt::Checked : Qt::Unchecked); showMessier = new QListWidgetItem(i18n("Messier Catalog (symbols)"), CatalogList); showMessier->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); showMessier->setCheckState(Options::showMessier() ? Qt::Checked : Qt::Unchecked); } void OpsCatalog::populateCustomCatalogs() { QStringList toggleNames = Options::showCatalogNames(); QStringList customList = *m_CustomCatalogFile; // Create a copy QStringListIterator catalogIter(customList); while (catalogIter.hasNext()) { QString catalogname = catalogIter.next(); //Skip already existing items if (CatalogList->findItems(catalogname, Qt::MatchExactly).length() > 0) continue; //Allocate new catalog list item QListWidgetItem *newItem = new QListWidgetItem(catalogname, CatalogList); newItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); if (toggleNames.contains(catalogname)) { newItem->setCheckState(Qt::Checked); } else { newItem->setCheckState(Qt::Unchecked); } } } diff --git a/kstars/options/opscolors.cpp b/kstars/options/opscolors.cpp index c7e599a19..ee7d48ec8 100644 --- a/kstars/options/opscolors.cpp +++ b/kstars/options/opscolors.cpp @@ -1,338 +1,338 @@ /*************************************************************************** opscolors.cpp - K Desktop Planetarium ------------------- begin : Sun Feb 29 2004 copyright : (C) 2004 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 "opscolors.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kspaths.h" #include "kstars.h" #include "kstarsdata.h" #include "skymap.h" #include "colorscheme.h" #ifdef HAVE_CFITSIO #include "fitsviewer/fitsviewer.h" #include "fitsviewer/fitsview.h" #endif #include "skyobjects/starobject.h" static int ItemColorData = Qt::UserRole + 1; OpsColors::OpsColors() : QFrame(KStars::Instance()) { setupUi(this); //Populate list of adjustable colors for (unsigned int i = 0; i < KStarsData::Instance()->colorScheme()->numberOfColors(); ++i) { QPixmap col(30, 20); QColor itemColor(KStarsData::Instance()->colorScheme()->colorAt(i)); col.fill(itemColor); QListWidgetItem *item = new QListWidgetItem(KStarsData::Instance()->colorScheme()->nameAt(i), ColorPalette); item->setData(Qt::DecorationRole, col); item->setData(ItemColorData, itemColor); } PresetBox->addItem(i18nc("use default color scheme", "Default Colors")); PresetBox->addItem(i18nc("use 'star chart' color scheme", "Star Chart")); PresetBox->addItem(i18nc("use 'night vision' color scheme", "Night Vision")); PresetBox->addItem(i18nc("use 'moonless night' color scheme", "Moonless Night")); PresetFileList.append("classic.colors"); PresetFileList.append("chart.colors"); PresetFileList.append("night.colors"); PresetFileList.append("moonless-night.colors"); QFile file; QString line, schemeName, filename; file.setFileName(KSPaths::locate(QStandardPaths::GenericDataLocation, "colors.dat")); if (file.exists() && file.open(QIODevice::ReadOnly)) { QTextStream stream(&file); while (!stream.atEnd()) { line = stream.readLine(); schemeName = line.left(line.indexOf(':')); filename = line.mid(line.indexOf(':') + 1, line.length()); PresetBox->addItem(schemeName); PresetFileList.append(filename); } file.close(); } kcfg_StarColorIntensity->setValue(KStarsData::Instance()->colorScheme()->starColorIntensity()); kcfg_StarColorMode->addItem(i18nc("use realistic star colors", "Real Colors")); kcfg_StarColorMode->addItem(i18nc("show stars as red circles", "Solid Red")); kcfg_StarColorMode->addItem(i18nc("show stars as black circles", "Solid Black")); kcfg_StarColorMode->addItem(i18nc("show stars as white circles", "Solid White")); kcfg_StarColorMode->addItem(i18nc("show stars as colored circles", "Solid Colors")); kcfg_StarColorMode->setCurrentIndex(KStarsData::Instance()->colorScheme()->starColorMode()); if (KStarsData::Instance()->colorScheme()->starColorMode() != 0) //mode is not "Real Colors" kcfg_StarColorIntensity->setEnabled(false); else kcfg_StarColorIntensity->setEnabled(true); kcfg_DarkAppColors->setChecked(KStarsData::Instance()->colorScheme()->useDarkPalette()); connect(ColorPalette, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(newColor(QListWidgetItem*))); connect(kcfg_StarColorIntensity, SIGNAL(valueChanged(int)), this, SLOT(slotStarColorIntensity(int))); connect(kcfg_StarColorMode, SIGNAL(activated(int)), this, SLOT(slotStarColorMode(int))); connect(kcfg_DarkAppColors, SIGNAL(toggled(bool)), this, SLOT(slotDarkAppColors(bool))); connect(PresetBox, SIGNAL(currentRowChanged(int)), this, SLOT(slotPreset(int))); connect(AddPreset, SIGNAL(clicked()), this, SLOT(slotAddPreset())); connect(RemovePreset, SIGNAL(clicked()), this, SLOT(slotRemovePreset())); connect(SavePreset, SIGNAL(clicked()), this, SLOT(slotSavePreset())); RemovePreset->setEnabled(false); SavePreset->setEnabled(false); } //empty destructor OpsColors::~OpsColors() { } void OpsColors::newColor(QListWidgetItem *item) { if (!item) return; QPixmap pixmap(30, 20); QColor NewColor; int index = ColorPalette->row(item); if (index < 0 || index >= ColorPalette->count()) return; QColor col = item->data(ItemColorData).value(); NewColor = QColorDialog::getColor(col); //NewColor will only be valid if the above if statement was found to be true during one of the for loop iterations if (NewColor.isValid()) { pixmap.fill(NewColor); item->setData(Qt::DecorationRole, pixmap); item->setData(ItemColorData, NewColor); KStarsData::Instance()->colorScheme()->setColor(KStarsData::Instance()->colorScheme()->keyAt(index), NewColor.name()); } if (PresetBox->currentRow() > 3) SavePreset->setEnabled(true); KStars::Instance()->map()->forceUpdate(); #ifdef HAVE_CFITSIO QList viewers = KStars::Instance()->findChildren(); foreach (FITSViewer *viewer, viewers) viewer->getCurrentView()->updateFrame(); #endif } void OpsColors::slotPreset(int index) { QString sPreset = PresetFileList.at(index); setColors(sPreset); } bool OpsColors::setColors(const QString &filename) { QPixmap temp(30, 20); //check if colorscheme is removable... QFile test; //try filename in local user KDE directory tree. test.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + filename); if (test.exists()) { RemovePreset->setEnabled(true); SavePreset->setEnabled(true); } else { RemovePreset->setEnabled(false); SavePreset->setEnabled(false); } test.close(); QString actionName = QString("cs_" + filename.left(filename.indexOf(".colors"))).toUtf8(); QAction *a = KStars::Instance()->actionCollection()->action(actionName); if (a) a->setChecked(true); qApp->processEvents(); kcfg_StarColorMode->setCurrentIndex(KStarsData::Instance()->colorScheme()->starColorMode()); kcfg_StarColorIntensity->setValue(KStarsData::Instance()->colorScheme()->starColorIntensity()); kcfg_DarkAppColors->setChecked(KStarsData::Instance()->colorScheme()->useDarkPalette()); for (unsigned int i = 0; i < KStarsData::Instance()->colorScheme()->numberOfColors(); ++i) { QColor itemColor(KStarsData::Instance()->colorScheme()->colorAt(i)); temp.fill(itemColor); ColorPalette->item(i)->setData(Qt::DecorationRole, temp); ColorPalette->item(i)->setData(ItemColorData, itemColor); } KStars::Instance()->map()->forceUpdate(); #ifdef HAVE_CFITSIO QList viewers = KStars::Instance()->findChildren(); foreach (FITSViewer *viewer, viewers) viewer->getCurrentView()->updateFrame(); #endif return true; } void OpsColors::slotAddPreset() { bool okPressed = false; QString schemename = - QInputDialog::getText(0, i18n("New Color Scheme"), i18n("Enter a name for the new color scheme:"), - QLineEdit::Normal, QString(), &okPressed, 0); + QInputDialog::getText(nullptr, i18n("New Color Scheme"), i18n("Enter a name for the new color scheme:"), + QLineEdit::Normal, QString(), &okPressed, nullptr); if (okPressed && !schemename.isEmpty()) { if (KStarsData::Instance()->colorScheme()->save(schemename)) { QListWidgetItem *item = new QListWidgetItem(schemename, PresetBox); QString fname = KStarsData::Instance()->colorScheme()->fileName(); PresetFileList.append(fname); QString actionName = QString("cs_" + fname.left(fname.indexOf(".colors"))).toUtf8(); KStars::Instance()->addColorMenuItem(schemename, actionName); QAction *a = KStars::Instance()->actionCollection()->action(actionName); if (a) a->setChecked(true); PresetBox->setCurrentItem(item); } } } void OpsColors::slotSavePreset() { QString schemename = PresetBox->currentItem()->text(); KStarsData::Instance()->colorScheme()->save(schemename); SavePreset->setEnabled(false); } void OpsColors::slotRemovePreset() { QListWidgetItem *current = PresetBox->currentItem(); if (!current) return; QString name = current->text(); QString filename = PresetFileList[PresetBox->currentRow()]; QFile cdatFile; cdatFile.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "colors.dat"); //determine filename in local user KDE directory tree. //Remove action from color-schemes menu KStars::Instance()->removeColorMenuItem(QString("cs_" + filename.left(filename.indexOf(".colors"))).toUtf8()); if (!cdatFile.exists() || !cdatFile.open(QIODevice::ReadWrite)) { QString message = i18n("Local color scheme index file could not be opened.\nScheme cannot be removed."); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); } else { //Remove entry from the ListBox and from the QStringList holding filenames. //There seems to be no way to set no item selected, so select // the first item. PresetBox->setCurrentRow(0); PresetFileList.removeOne(filename); delete current; RemovePreset->setEnabled(false); //Read the contents of colors.dat into a QStringList, except for the entry to be removed. QTextStream stream(&cdatFile); QStringList slist; bool removed = false; while (!stream.atEnd()) { QString line = stream.readLine(); if (line.left(line.indexOf(':')) != name) slist.append(line); else removed = true; } if (removed) //Entry was removed; delete the corresponding .colors file. { QFile colorFile; colorFile.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + filename); //determine filename in local user KDE directory tree. if (!colorFile.remove()) { QString message = i18n("Could not delete the file: %1", colorFile.fileName()); - KMessageBox::sorry(0, message, i18n("Error Deleting File")); + KMessageBox::sorry(nullptr, message, i18n("Error Deleting File")); } //remove the old colors.dat file, and rebuild it with the modified string list. cdatFile.remove(); cdatFile.open(QIODevice::ReadWrite); QTextStream stream2(&cdatFile); for (int i = 0; i < slist.count(); ++i) stream << slist[i] << endl; } else { QString message = i18n("Could not find an entry named %1 in colors.dat.", name); - KMessageBox::sorry(0, message, i18n("Scheme Not Found")); + KMessageBox::sorry(nullptr, message, i18n("Scheme Not Found")); } cdatFile.close(); } } void OpsColors::slotStarColorMode(int i) { KStarsData::Instance()->colorScheme()->setStarColorMode(i); if (KStarsData::Instance()->colorScheme()->starColorMode() != 0) //mode is not "Real Colors" kcfg_StarColorIntensity->setEnabled(false); else kcfg_StarColorIntensity->setEnabled(true); } void OpsColors::slotStarColorIntensity(int i) { KStarsData::Instance()->colorScheme()->setStarColorIntensity(i); } void OpsColors::slotDarkAppColors(bool enable) { KStarsData::Instance()->colorScheme()->setDarkPalette(enable); KStars::Instance()->loadColorScheme(KStars::Instance()->data()->colorScheme()->fileName()); } diff --git a/kstars/options/opssatellites.cpp b/kstars/options/opssatellites.cpp index e1e4f8892..bf57d8f4c 100644 --- a/kstars/options/opssatellites.cpp +++ b/kstars/options/opssatellites.cpp @@ -1,299 +1,299 @@ /*************************************************************************** opssatellites.cpp - K Desktop Planetarium ------------------- begin : Mon 21 Mar 2011 copyright : (C) 2011 by Jérôme SONRIER email : jsid@emor3j.fr.eu.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 "opssatellites.h" #include #include #include "kstars.h" #include "kstarsdata.h" #include "Options.h" #include "satellite.h" #include "skymapcomposite.h" #include "skycomponents/satellitescomponent.h" #include "skymap.h" static const char *satgroup_strings_context = "Satellite group name"; SatelliteSortFilterProxyModel::SatelliteSortFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { } bool SatelliteSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); if (sourceModel()->hasChildren(index)) { for (int i = 0; i < sourceModel()->rowCount(index); ++i) if (filterAcceptsRow(i, index)) return true; return false; } return sourceModel()->data(index).toString().contains(filterRegExp()); } OpsSatellites::OpsSatellites() : QFrame(KStars::Instance()) { setupUi(this); m_ConfigDialog = KConfigDialog::exists("settings"); //Set up the Table Views m_Model = new QStandardItemModel(0, 1, this); m_SortModel = new SatelliteSortFilterProxyModel(this); m_SortModel->setSourceModel(m_Model); SatListTreeView->setModel(m_SortModel); SatListTreeView->setEditTriggers(QTreeView::NoEditTriggers); SatListTreeView->setSortingEnabled(false); // Populate satellites list updateListView(); // Signals and slots connections connect(UpdateTLEButton, SIGNAL(clicked()), this, SLOT(slotUpdateTLEs())); connect(kcfg_ShowSatellites, SIGNAL(toggled(bool)), SLOT(slotShowSatellites(bool))); connect(m_ConfigDialog->button(QDialogButtonBox::Apply), SIGNAL(clicked()), SLOT(slotApply())); connect(m_ConfigDialog->button(QDialogButtonBox::Ok), SIGNAL(clicked()), SLOT(slotApply())); connect(m_ConfigDialog->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), SLOT(slotCancel())); connect(FilterEdit, SIGNAL(textChanged(QString)), this, SLOT(slotFilterReg(QString))); connect(m_Model, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotItemChanged(QStandardItem*))); connect(satelliteButtonGroup, static_cast(&QButtonGroup::buttonPressed), this, [&]() { isDirty = true; }); isDirty = false; } OpsSatellites::~OpsSatellites() { } void OpsSatellites::slotUpdateTLEs() { // Save existing satellites saveSatellitesList(); // Get new data files KStarsData::Instance()->skyComposite()->satellites()->updateTLEs(); isDirty = true; // Refresh satellites list updateListView(); } void OpsSatellites::updateListView() { KStarsData *data = KStarsData::Instance(); // Clear satellites list m_Model->clear(); SatListTreeView->reset(); m_Model->setHorizontalHeaderLabels(QStringList(i18n("Satellite name"))); // Add each groups and satellites in the list foreach (SatelliteGroup *sat_group, data->skyComposite()->satellites()->groups()) { QStandardItem *group_item; QStandardItem *sat_item; bool all_sat_checked = true; bool all_sat_unchecked = true; // Add the group group_item = new QStandardItem(i18nc(satgroup_strings_context, sat_group->name().toUtf8())); group_item->setCheckable(true); m_Model->appendRow(group_item); // Add all satellites of the group for (int i = 0; i < sat_group->count(); ++i) { sat_item = new QStandardItem(sat_group->at(i)->name()); sat_item->setCheckable(true); if (Options::selectedSatellites().contains(sat_group->at(i)->name())) { sat_item->setCheckState(Qt::Checked); all_sat_unchecked = false; } else all_sat_checked = false; group_item->setChild(i, sat_item); } // If all satellites of the group are selected, select the group if (all_sat_checked) group_item->setCheckState(Qt::Checked); else if (all_sat_unchecked) group_item->setCheckState(Qt::Unchecked); else group_item->setCheckState(Qt::PartiallyChecked); } } void OpsSatellites::saveSatellitesList() { KStarsData *data = KStarsData::Instance(); QString sat_name; QStringList selected_satellites; QModelIndex group_index, sat_index; QStandardItem *group_item; QStandardItem *sat_item; // Retrieve each satellite in the list and select it if checkbox is checked for (int i = 0; i < m_Model->rowCount(SatListTreeView->rootIndex()); ++i) { group_index = m_Model->index(i, 0, SatListTreeView->rootIndex()); group_item = m_Model->itemFromIndex(group_index); for (int j = 0; j < m_Model->rowCount(group_item->index()); ++j) { sat_index = m_Model->index(j, 0, group_index); sat_item = m_Model->itemFromIndex(sat_index); sat_name = sat_item->data(0).toString(); Satellite *sat = data->skyComposite()->satellites()->findSatellite(sat_name); if (sat) { if (sat_item->checkState() == Qt::Checked) { int rc = sat->updatePos(); // If position calculation fails, unselect it if (rc == 0) { sat->setSelected(true); selected_satellites.append(sat_name); } else { KStars::Instance()->statusBar()->showMessage( i18n("%1 position calculation error: %2.", sat->name(), sat->sgp4ErrorString(rc)), 0); sat->setSelected(false); sat_item->setCheckState(Qt::Unchecked); } } else { sat->setSelected(false); } } } } Options::setSelectedSatellites(selected_satellites); } void OpsSatellites::slotApply() { if (isDirty == false) return; isDirty = false; saveSatellitesList(); // update time for all objects because they might be not initialized // it's needed when using horizontal coordinates KStars::Instance()->data()->setFullTimeUpdate(); KStars::Instance()->updateTime(); KStars::Instance()->map()->forceUpdate(); } void OpsSatellites::slotCancel() { // Update satellites list updateListView(); } void OpsSatellites::slotShowSatellites(bool on) { isDirty = true; kcfg_ShowVisibleSatellites->setEnabled(on); kcfg_ShowSatellitesLabels->setEnabled(on); kcfg_DrawSatellitesLikeStars->setEnabled(on); } void OpsSatellites::slotFilterReg(const QString &filter) { m_SortModel->setFilterRegExp(QRegExp(filter, Qt::CaseInsensitive, QRegExp::RegExp)); m_SortModel->setFilterKeyColumn(-1); isDirty = true; // Expand all categories when the user use filter if (filter.length() > 0) SatListTreeView->expandAll(); else SatListTreeView->collapseAll(); } void OpsSatellites::slotItemChanged(QStandardItem *item) { - if (item->parent() == 0 && !item->hasChildren()) + if (item->parent() == nullptr && !item->hasChildren()) { return; } isDirty = true; QModelIndex sat_index; QStandardItem *sat_item; disconnect(m_Model, SIGNAL(itemChanged(QStandardItem *)), this, SLOT(slotItemChanged(QStandardItem *))); // If a group has been (un)checked, (un)check all satellites of the group // else a satellite has been (un)checked, (un)check his group if (item->hasChildren()) { for (int i = 0; i < m_Model->rowCount(item->index()); ++i) { sat_index = m_Model->index(i, 0, item->index()); sat_item = m_Model->itemFromIndex(sat_index); if (item->checkState() == Qt::Checked) sat_item->setCheckState(Qt::Checked); else sat_item->setCheckState(Qt::Unchecked); } } else { bool all_sat_checked = true; bool all_sat_unchecked = true; for (int i = 0; i < item->parent()->model()->rowCount(item->parent()->index()); ++i) { sat_index = m_Model->index(i, 0, item->parent()->index()); sat_item = m_Model->itemFromIndex(sat_index); if (sat_item->checkState() == Qt::Checked) all_sat_unchecked = false; else all_sat_checked = false; } if (all_sat_checked) item->parent()->setCheckState(Qt::Checked); else if (all_sat_unchecked) item->parent()->setCheckState(Qt::Unchecked); else item->parent()->setCheckState(Qt::PartiallyChecked); } connect(m_Model, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotItemChanged(QStandardItem*))); } diff --git a/kstars/printing/detailstable.cpp b/kstars/printing/detailstable.cpp index d8413c4c9..21f3346cf 100644 --- a/kstars/printing/detailstable.cpp +++ b/kstars/printing/detailstable.cpp @@ -1,729 +1,729 @@ /*************************************************************************** detailstable.cpp - K Desktop Planetarium ------------------- begin : Fri Jul 29 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #include "detailstable.h" #include #include #include "kstars.h" #include "kstarsdata.h" #include "ksplanetbase.h" #include "starobject.h" #include "skymapcomposite.h" #include "constellationboundarylines.h" #include "deepskyobject.h" #include "catalogcomponent.h" #include "ksmoon.h" #include "ksasteroid.h" #include "kscomet.h" DetailsTable::DetailsTable() { m_Document = new QTextDocument(KStars::Instance()); setDefaultFormatting(); } DetailsTable::~DetailsTable() { if (m_Document) { delete m_Document; } } void DetailsTable::createGeneralTable(SkyObject *obj) { clearContents(); QTextCursor cursor = m_Document->rootFrame()->firstCursorPosition(); //Fill in the data fields //Contents depend on type of object - StarObject *s = 0; - DeepSkyObject *dso = 0; - KSPlanetBase *ps = 0; + StarObject *s = nullptr; + DeepSkyObject *dso = nullptr; + KSPlanetBase *ps = nullptr; QString pname, oname; QString objNamesVal, objTypeVal, objDistVal, objSizeVal, objMagVal, objBvVal, objIllumVal; QString objSizeLabel, objMagLabel; switch (obj->type()) { case SkyObject::STAR: { s = (StarObject *)obj; objNamesVal = s->longname(); if (s->getHDIndex() != 0) { if (!s->longname().isEmpty()) { objNamesVal = s->longname() + QString(", HD%1").arg(QString::number(s->getHDIndex())); } else { objNamesVal = QString(", HD%1").arg(QString::number(s->getHDIndex())); } } objTypeVal = s->sptype() + ' ' + i18n("star"); objMagVal = i18nc("number in magnitudes", "%1 mag", QLocale().toString(s->mag(), 1)); //show to tenths place if (s->getBVIndex() < 30.0) { objBvVal = QString::number(s->getBVIndex(), 'g', 2); } //distance if (s->distance() > 2000. || s->distance() < 0.) // parallax < 0.5 mas { objDistVal = i18nc("larger than 2000 parsecs", "> 2000 pc"); } else if (s->distance() > 50.0) //show to nearest integer { objDistVal = i18nc("number in parsecs", "%1 pc", QLocale().toString(s->distance(), 0)); } else if (s->distance() > 10.0) //show to tenths place { objDistVal = i18nc("number in parsecs", "%1 pc", QLocale().toString(s->distance(), 1)); } else //show to hundredths place { objDistVal = i18nc("number in parsecs", "%1 pc", QLocale().toString(s->distance(), 2)); } //Note multiplicity/variablility in angular size label if (s->isMultiple() && s->isVariable()) { objSizeLabel = i18nc("the star is a multiple star", "multiple") + ','; objSizeVal = i18nc("the star is a variable star", "variable"); } else if (s->isMultiple()) { objSizeLabel = i18nc("the star is a multiple star", "multiple"); } else if (s->isVariable()) { objSizeLabel = i18nc("the star is a variable star", "variable"); } objIllumVal = "--"; break; //End of stars case } case SkyObject::ASTEROID: //[fall through to planets] case SkyObject::COMET: //[fall through to planets] case SkyObject::MOON: //[fall through to planets] case SkyObject::PLANET: { ps = (KSPlanetBase *)obj; objNamesVal = ps->longname(); //Type is "G5 star" for Sun if (ps->name() == "Sun") { objTypeVal = i18n("G5 star"); } else if (ps->name() == "Moon") { objTypeVal = ps->translatedName(); } else if (ps->name() == i18n("Pluto") || ps->name() == "Ceres" || ps->name() == "Eris") // TODO: Check if Ceres / Eris have translations and i18n() them { objTypeVal = i18n("Dwarf planet"); } else { objTypeVal = ps->typeName(); } //Magnitude: The moon displays illumination fraction instead if (obj->name() == "Moon") { objIllumVal = QString("%1 %").arg(QLocale().toString(((KSMoon *)obj)->illum() * 100., 0)); } objMagVal = i18nc("number in magnitudes", "%1 mag", QLocale().toString(ps->mag(), 1)); //show to tenths place //Distance from Earth. The moon requires a unit conversion if (ps->name() == "Moon") { objDistVal = i18nc("distance in kilometers", "%1 km", QLocale().toString(ps->rearth() * AU_KM)); } else { objDistVal = i18nc("distance in Astronomical Units", "%1 AU", QLocale().toString(ps->rearth())); } //Angular size; moon and sun in arcmin, others in arcsec if (ps->angSize()) { if (ps->name() == "Sun" || ps->name() == "Moon") { // Needn't be a plural form because sun / moon will never contract to 1 arcminute objSizeVal = i18nc("angular size in arcminutes", "%1 arcmin", QLocale().toString(ps->angSize())); } else { objSizeVal = i18nc("angular size in arcseconds", "%1 arcsec", QLocale().toString(ps->angSize() * 60.0)); } } else { objSizeVal = "--"; } break; //End of planets/comets/asteroids case } default: //Deep-sky objects { dso = (DeepSkyObject *)obj; //Show all names recorded for the object if (!dso->longname().isEmpty() && dso->longname() != dso->name()) { pname = dso->translatedLongName(); oname = dso->translatedName(); } else { pname = dso->translatedName(); } if (!dso->translatedName2().isEmpty()) { if (oname.isEmpty()) { oname = dso->translatedName2(); } else { oname += ", " + dso->translatedName2(); } } if (dso->ugc() != 0) { if (!oname.isEmpty()) { oname += ", "; } oname += "UGC " + QString::number(dso->ugc()); } if (dso->pgc() != 0) { if (!oname.isEmpty()) { oname += ", "; } oname += "PGC " + QString::number(dso->pgc()); } if (!oname.isEmpty()) { pname += ", " + oname; } objNamesVal = pname; objTypeVal = dso->typeName(); if (dso->type() == SkyObject::RADIO_SOURCE) { Q_ASSERT(dso->customCatalog()); objMagLabel = i18nc("integrated flux at a frequency", "Flux(%1):", dso->customCatalog()->fluxFrequency()); objMagVal = i18nc("integrated flux value", "%1 %2", QLocale().toString(dso->flux(), 1), dso->customCatalog()->fluxUnit()); //show to tenths place } else if (dso->mag() > 90.0) { objMagVal = "--"; } else { objMagVal = i18nc("number in magnitudes", "%1 mag", QLocale().toString(dso->mag(), 1)); //show to tenths place } //No distances at this point... objDistVal = "--"; //Only show decimal place for small angular sizes if (dso->a() > 10.0) { objSizeVal = i18nc("angular size in arcminutes", "%1 arcmin", QLocale().toString(dso->a(), 0)); } else if (dso->a()) { objSizeVal = i18nc("angular size in arcminutes", "%1 arcmin", QLocale().toString(dso->a(), 1)); } else { objSizeVal = "--"; } break; //End of deep-space objects case } } //Common to all types: if (obj->type() == SkyObject::CONSTELLATION) { objTypeVal = KStarsData::Instance()->skyComposite()->constellationBoundary()->constellationName(obj); } else { objTypeVal = i18nc("%1 type of sky object (planet, asteroid etc), %2 name of a constellation", "%1 in %2", objTypeVal, KStarsData::Instance()->skyComposite()->constellationBoundary()->constellationName(obj)); } QVector constraints; constraints << QTextLength(QTextLength::PercentageLength, 25) << QTextLength(QTextLength::PercentageLength, 25) << QTextLength(QTextLength::PercentageLength, 25) << QTextLength(QTextLength::PercentageLength, 25); m_TableFormat.setColumnWidthConstraints(constraints); QTextTable *table = cursor.insertTable(5, 4, m_TableFormat); table->mergeCells(0, 0, 1, 4); QTextBlockFormat centered; centered.setAlignment(Qt::AlignCenter); table->cellAt(0, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(0, 0).firstCursorPosition().insertText(i18n("General"), m_TableTitleCharFormat); table->mergeCells(1, 1, 1, 3); table->cellAt(1, 0).firstCursorPosition().insertText(i18n("Names:"), m_ItemNameCharFormat); table->cellAt(1, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(1, 1).firstCursorPosition().insertText(objNamesVal, m_ItemValueCharFormat); table->cellAt(2, 0).firstCursorPosition().insertText(i18n("Type:"), m_ItemNameCharFormat); table->cellAt(2, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(2, 1).firstCursorPosition().insertText(objTypeVal, m_ItemValueCharFormat); table->cellAt(3, 0).firstCursorPosition().insertText(i18n("Distance:"), m_ItemNameCharFormat); table->cellAt(3, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(3, 1).firstCursorPosition().insertText(objDistVal, m_ItemValueCharFormat); table->cellAt(4, 0).firstCursorPosition().insertText(i18n("Size:"), m_ItemNameCharFormat); table->cellAt(4, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(4, 1).firstCursorPosition().insertText(objSizeVal, m_ItemValueCharFormat); table->cellAt(2, 2).firstCursorPosition().insertText(i18n("Magnitude:"), m_ItemNameCharFormat); table->cellAt(2, 2).firstCursorPosition().setBlockFormat(centered); table->cellAt(2, 3).firstCursorPosition().insertText(objMagVal, m_ItemValueCharFormat); table->cellAt(3, 2).firstCursorPosition().insertText(i18n("B-V index:"), m_ItemNameCharFormat); table->cellAt(3, 2).firstCursorPosition().setBlockFormat(centered); table->cellAt(3, 3).firstCursorPosition().insertText(objBvVal, m_ItemValueCharFormat); table->cellAt(4, 2).firstCursorPosition().insertText(i18n("Illumination:"), m_ItemNameCharFormat); table->cellAt(4, 2).firstCursorPosition().setBlockFormat(centered); table->cellAt(4, 3).firstCursorPosition().insertText(objIllumVal, m_ItemValueCharFormat); } void DetailsTable::createAsteroidCometTable(SkyObject *obj) { clearContents(); QTextCursor cursor = m_Document->rootFrame()->firstCursorPosition(); QString perihelionVal, orbitIdVal, neoVal, diamVal, rotPeriodVal, moidVal; QString orbitClassVal, albedoVal, dimVal, periodVal; // Add specifics data switch (obj->type()) { case SkyObject::ASTEROID: { KSAsteroid *ast = (KSAsteroid *)obj; // Perihelion perihelionVal = QString::number(ast->getPerihelion()) + " AU"; // Earth MOID moidVal = ast->getEarthMOID() == 0 ? QString("--") : QString::number(ast->getEarthMOID()) + QString(" AU"); // Orbit ID orbitIdVal = ast->getOrbitID(); // Orbit Class orbitClassVal = ast->getOrbitClass(); // NEO neoVal = ast->isNEO() ? i18n("Yes") : i18n("No"); // Albedo albedoVal = ast->getAlbedo() == 0 ? QString("--") : QString::number(ast->getAlbedo()); // Diameter diamVal = ast->getDiameter() == 0 ? QString("--") : QString::number(ast->getDiameter()) + QString(" km"); // Dimensions dimVal = ast->getDimensions().isEmpty() ? QString("--") : ast->getDimensions() + QString(" km"); // Rotation period rotPeriodVal = ast->getRotationPeriod() == 0 ? QString("--") : QString::number(ast->getRotationPeriod()) + QString(" h"); // Period periodVal = ast->getPeriod() == 0 ? QString("--") : QString::number(ast->getPeriod()) + QString(" y"); break; } case SkyObject::COMET: { KSComet *com = (KSComet *)obj; // Perihelion perihelionVal = QString::number(com->getPerihelion()) + " AU"; // Earth MOID moidVal = com->getEarthMOID() == 0 ? QString("--") : QString::number(com->getEarthMOID()) + QString(" AU"); // Orbit ID orbitIdVal = com->getOrbitID(); // Orbit Class orbitClassVal = com->getOrbitClass(); // NEO neoVal = com->isNEO() ? i18n("Yes") : i18n("No"); // Albedo albedoVal = com->getAlbedo() == 0 ? QString("--") : QString::number(com->getAlbedo()); // Diameter diamVal = com->getDiameter() == 0 ? QString("--") : QString::number(com->getDiameter()) + QString(" km"); // Dimensions dimVal = com->getDimensions().isEmpty() ? QString("--") : com->getDimensions() + QString(" km"); // Rotation period rotPeriodVal = com->getRotationPeriod() == 0 ? QString("--") : QString::number(com->getRotationPeriod()) + QString(" h"); // Period periodVal = com->getPeriod() == 0 ? QString("--") : QString::number(com->getPeriod()) + QString(" y"); break; } default: { return; } } // Set column width constraints QVector constraints; constraints << QTextLength(QTextLength::PercentageLength, 25) << QTextLength(QTextLength::PercentageLength, 25) << QTextLength(QTextLength::PercentageLength, 25) << QTextLength(QTextLength::PercentageLength, 25); m_TableFormat.setColumnWidthConstraints(constraints); QTextTable *table = cursor.insertTable(6, 4, m_TableFormat); table->mergeCells(0, 0, 1, 4); QTextBlockFormat centered; centered.setAlignment(Qt::AlignCenter); table->cellAt(0, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(0, 0).firstCursorPosition().insertText(i18n("Asteroid/Comet details"), m_TableTitleCharFormat); table->cellAt(1, 0).firstCursorPosition().insertText(i18n("Perihelion:"), m_ItemNameCharFormat); table->cellAt(1, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(1, 1).firstCursorPosition().insertText(perihelionVal, m_ItemValueCharFormat); table->cellAt(2, 0).firstCursorPosition().insertText(i18n("Orbit ID:"), m_ItemNameCharFormat); table->cellAt(2, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(2, 1).firstCursorPosition().insertText(orbitIdVal, m_ItemValueCharFormat); table->cellAt(3, 0).firstCursorPosition().insertText(i18n("NEO:"), m_ItemNameCharFormat); table->cellAt(3, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(3, 1).firstCursorPosition().insertText(neoVal, m_ItemValueCharFormat); table->cellAt(4, 0).firstCursorPosition().insertText(i18n("Diameter:"), m_ItemNameCharFormat); table->cellAt(4, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(4, 1).firstCursorPosition().insertText(diamVal, m_ItemValueCharFormat); table->cellAt(5, 0).firstCursorPosition().insertText(i18n("Rotation period:"), m_ItemNameCharFormat); table->cellAt(5, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(5, 1).firstCursorPosition().insertText(rotPeriodVal, m_ItemValueCharFormat); table->cellAt(1, 2).firstCursorPosition().insertText(i18n("Earth MOID:"), m_ItemNameCharFormat); table->cellAt(1, 2).firstCursorPosition().setBlockFormat(centered); table->cellAt(1, 3).firstCursorPosition().insertText(moidVal, m_ItemValueCharFormat); table->cellAt(2, 2).firstCursorPosition().insertText(i18n("Orbit class:"), m_ItemNameCharFormat); table->cellAt(2, 2).firstCursorPosition().setBlockFormat(centered); table->cellAt(2, 3).firstCursorPosition().insertText(orbitClassVal, m_ItemValueCharFormat); table->cellAt(3, 2).firstCursorPosition().insertText(i18n("Albedo:"), m_ItemNameCharFormat); table->cellAt(3, 2).firstCursorPosition().setBlockFormat(centered); table->cellAt(3, 3).firstCursorPosition().insertText(albedoVal, m_ItemValueCharFormat); table->cellAt(4, 2).firstCursorPosition().insertText(i18n("Dimensions:"), m_ItemNameCharFormat); table->cellAt(4, 2).firstCursorPosition().setBlockFormat(centered); table->cellAt(4, 3).firstCursorPosition().insertText(dimVal, m_ItemValueCharFormat); table->cellAt(5, 2).firstCursorPosition().insertText(i18n("Period:"), m_ItemNameCharFormat); table->cellAt(5, 2).firstCursorPosition().setBlockFormat(centered); table->cellAt(5, 3).firstCursorPosition().insertText(periodVal, m_ItemValueCharFormat); } void DetailsTable::createCoordinatesTable(SkyObject *obj, const KStarsDateTime &ut, GeoLocation *geo) { clearContents(); QTextCursor cursor = m_Document->rootFrame()->firstCursorPosition(); // Set column width constraints QVector constraints; constraints << QTextLength(QTextLength::PercentageLength, 25) << QTextLength(QTextLength::PercentageLength, 25) << QTextLength(QTextLength::PercentageLength, 25) << QTextLength(QTextLength::PercentageLength, 25); m_TableFormat.setColumnWidthConstraints(constraints); // Insert table & row containing table name QTextTable *table = cursor.insertTable(4, 4, m_TableFormat); table->mergeCells(0, 0, 1, 4); QTextBlockFormat centered; centered.setAlignment(Qt::AlignCenter); table->cellAt(0, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(0, 0).firstCursorPosition().insertText(i18n("Coordinates"), m_TableTitleCharFormat); //Coordinates Section: //Don't use KLocale::formatNumber() for the epoch string, //because we don't want a thousands-place separator! QString sEpoch = QString::number(ut.epoch(), 'f', 1); //Replace the decimal point with localized decimal symbol sEpoch.replace('.', QLocale().decimalPoint()); table->cellAt(1, 0).firstCursorPosition().insertText(i18n("RA (%1):", sEpoch), m_ItemNameCharFormat); table->cellAt(1, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(1, 1).firstCursorPosition().insertText(obj->ra().toHMSString(), m_ItemValueCharFormat); table->cellAt(2, 0).firstCursorPosition().insertText(i18n("Dec (%1):", sEpoch), m_ItemNameCharFormat); table->cellAt(2, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(2, 1).firstCursorPosition().insertText(obj->dec().toDMSString(), m_ItemValueCharFormat); table->cellAt(3, 0).firstCursorPosition().insertText(i18n("Hour angle:"), m_ItemNameCharFormat); table->cellAt(3, 0).firstCursorPosition().setBlockFormat(centered); //Hour Angle can be negative, but dms HMS expressions cannot. //Here's a kludgy workaround: dms lst = geo->GSTtoLST(ut.gst()); dms ha(lst.Degrees() - obj->ra().Degrees()); QChar sgn('+'); if (ha.Hours() > 12.0) { ha.setH(24.0 - ha.Hours()); sgn = '-'; } table->cellAt(3, 1).firstCursorPosition().insertText(QString("%1%2").arg(sgn).arg(ha.toHMSString()), m_ItemValueCharFormat); table->cellAt(1, 2).firstCursorPosition().insertText(i18n("Azimuth:"), m_ItemNameCharFormat); table->cellAt(1, 2).firstCursorPosition().setBlockFormat(centered); table->cellAt(1, 3).firstCursorPosition().insertText(obj->az().toDMSString(), m_ItemValueCharFormat); table->cellAt(2, 2).firstCursorPosition().insertText(i18n("Altitude:"), m_ItemNameCharFormat); table->cellAt(2, 2).firstCursorPosition().setBlockFormat(centered); dms a; if (Options::useAltAz()) { a = obj->alt(); } else { a = obj->altRefracted(); } table->cellAt(2, 3).firstCursorPosition().insertText(a.toDMSString(), m_ItemValueCharFormat); table->cellAt(3, 2).firstCursorPosition().insertText(i18n("Airmass:"), m_ItemNameCharFormat); table->cellAt(3, 2).firstCursorPosition().setBlockFormat(centered); //Airmass is approximated as the secant of the zenith distance, //equivalent to 1./sin(Alt). Beware of Inf at Alt=0! QString aMassStr; if (obj->alt().Degrees() > 0.0) { aMassStr = QLocale().toString(1. / sin(obj->alt().radians()), 2); } else { aMassStr = "--"; } table->cellAt(3, 3).firstCursorPosition().insertText(aMassStr, m_ItemValueCharFormat); // Restore the position and other time-dependent parameters obj->recomputeCoords(ut, geo); } void DetailsTable::createRSTTAble(SkyObject *obj, const KStarsDateTime &ut, GeoLocation *geo) { clearContents(); QTextCursor cursor = m_Document->rootFrame()->firstCursorPosition(); QString rtValue, stValue; // Rise/Set time values QString azRValue, azSValue; // Rise/Set azimuth values //Prepare time/position variables QTime rt = obj->riseSetTime(ut, geo, true); //true = use rise time dms raz = obj->riseSetTimeAz(ut, geo, true); //true = use rise time //If transit time is before rise time, use transit time for tomorrow QTime tt = obj->transitTime(ut, geo); dms talt = obj->transitAltitude(ut, geo); if (tt < rt) { tt = obj->transitTime(ut.addDays(1), geo); talt = obj->transitAltitude(ut.addDays(1), geo); } //If set time is before rise time, use set time for tomorrow QTime st = obj->riseSetTime(ut, geo, false); //false = use set time dms saz = obj->riseSetTimeAz(ut, geo, false); //false = use set time if (st < rt) { st = obj->riseSetTime(ut.addDays(1), geo, false); //false = use set time saz = obj->riseSetTimeAz(ut.addDays(1), geo, false); //false = use set time } if (rt.isValid()) { rtValue = QString().sprintf("%02d:%02d", rt.hour(), rt.minute()); stValue = QString().sprintf("%02d:%02d", st.hour(), st.minute()); azRValue = raz.toDMSString(); azSValue = saz.toDMSString(); } else { if (obj->alt().Degrees() > 0.0) { rtValue = i18n("Circumpolar"); stValue = i18n("Circumpolar"); } else { rtValue = i18n("Never rises"); stValue = i18n("Never rises"); } azRValue = i18nc("Not Applicable", "N/A"); azSValue = i18nc("Not Applicable", "N/A"); } // Set column width constraints QVector constraints; constraints << QTextLength(QTextLength::PercentageLength, 25) << QTextLength(QTextLength::PercentageLength, 25) << QTextLength(QTextLength::PercentageLength, 25) << QTextLength(QTextLength::PercentageLength, 25); m_TableFormat.setColumnWidthConstraints(constraints); // Insert table & row containing table name QTextTable *table = cursor.insertTable(4, 4, m_TableFormat); table->mergeCells(0, 0, 1, 4); QTextBlockFormat centered; centered.setAlignment(Qt::AlignCenter); table->cellAt(0, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(0, 0).firstCursorPosition().insertText(i18n("Rise/Set/Transit"), m_TableTitleCharFormat); // Insert cell names & values table->cellAt(1, 0).firstCursorPosition().insertText(i18n("Rise time:"), m_ItemNameCharFormat); table->cellAt(1, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(1, 1).firstCursorPosition().insertText(rtValue, m_ItemValueCharFormat); table->cellAt(2, 0).firstCursorPosition().insertText(i18n("Transit time:"), m_ItemNameCharFormat); table->cellAt(2, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(2, 1).firstCursorPosition().insertText(QString().sprintf("%02d:%02d", tt.hour(), tt.minute()), m_ItemValueCharFormat); table->cellAt(3, 0).firstCursorPosition().insertText(i18n("Set time:"), m_ItemNameCharFormat); table->cellAt(3, 0).firstCursorPosition().setBlockFormat(centered); table->cellAt(3, 1).firstCursorPosition().insertText(stValue, m_ItemValueCharFormat); table->cellAt(1, 2).firstCursorPosition().insertText(i18n("Azimuth at rise:"), m_ItemNameCharFormat); table->cellAt(1, 2).firstCursorPosition().setBlockFormat(centered); table->cellAt(1, 3).firstCursorPosition().insertText(azRValue, m_ItemValueCharFormat); table->cellAt(2, 2).firstCursorPosition().insertText(i18n("Altitude at transit:"), m_ItemNameCharFormat); table->cellAt(2, 2).firstCursorPosition().setBlockFormat(centered); table->cellAt(2, 3).firstCursorPosition().insertText(talt.toDMSString(), m_ItemValueCharFormat); table->cellAt(3, 2).firstCursorPosition().insertText(i18n("Azimuth at set:"), m_ItemNameCharFormat); table->cellAt(3, 2).firstCursorPosition().setBlockFormat(centered); table->cellAt(3, 3).firstCursorPosition().insertText(azSValue, m_ItemValueCharFormat); // Restore the position and other time-dependent parameters obj->recomputeCoords(ut, geo); } void DetailsTable::clearContents() { m_Document->clear(); } void DetailsTable::setDefaultFormatting() { // Set default table format m_TableFormat.setAlignment(Qt::AlignCenter); m_TableFormat.setBorder(4); m_TableFormat.setCellPadding(2); m_TableFormat.setCellSpacing(2); // Set default table title character format m_TableTitleCharFormat.setFont(QFont("Times", 12, QFont::Bold)); m_TableTitleCharFormat.setFontCapitalization(QFont::Capitalize); // Set default table item name character format m_ItemNameCharFormat.setFont(QFont("Times", 10, QFont::Bold)); // Set default table item value character format m_ItemValueCharFormat.setFont(QFont("Times", 10)); } diff --git a/kstars/printing/foveditordialog.cpp b/kstars/printing/foveditordialog.cpp index 4334734e9..9e0c3d17e 100644 --- a/kstars/printing/foveditordialog.cpp +++ b/kstars/printing/foveditordialog.cpp @@ -1,274 +1,274 @@ /*************************************************************************** foveditordialog.cpp - K Desktop Planetarium ------------------- begin : Fri Aug 12 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #include "foveditordialog.h" #include "kstars.h" #include "printingwizard.h" #include #include #include #include #include #include FovEditorDialogUI::FovEditorDialogUI(QWidget *parent) : QFrame(parent) { setupUi(this); #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif setWindowTitle(i18n("Field of View Snapshot Browser")); } FovEditorDialog::FovEditorDialog(PrintingWizard *wizard, QWidget *parent) : QDialog(parent), m_ParentWizard(wizard), m_CurrentIndex(0) { m_EditorUi = new FovEditorDialogUI(this); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(m_EditorUi); setLayout(mainLayout); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); setupWidgets(); setupConnections(); } void FovEditorDialog::slotNextFov() { slotSaveDescription(); if (m_CurrentIndex < m_ParentWizard->getFovSnapshotList()->size() - 1) { m_CurrentIndex++; updateFovImage(); updateButtons(); updateDescriptions(); } } void FovEditorDialog::slotPreviousFov() { slotSaveDescription(); if (m_CurrentIndex > 0) { m_CurrentIndex--; updateFovImage(); updateButtons(); updateDescriptions(); } } void FovEditorDialog::slotCaptureAgain() { hide(); m_ParentWizard->recaptureFov(m_CurrentIndex); } void FovEditorDialog::slotDelete() { if (m_CurrentIndex > m_ParentWizard->getFovSnapshotList()->size() - 1) { return; } delete m_ParentWizard->getFovSnapshotList()->at(m_CurrentIndex); m_ParentWizard->getFovSnapshotList()->removeAt(m_CurrentIndex); if (m_CurrentIndex == m_ParentWizard->getFovSnapshotList()->size()) { m_CurrentIndex--; } updateFovImage(); updateButtons(); updateDescriptions(); } void FovEditorDialog::slotSaveDescription() { if (m_CurrentIndex < m_ParentWizard->getFovSnapshotList()->size()) { m_ParentWizard->getFovSnapshotList()->at(m_CurrentIndex)->setDescription(m_EditorUi->descriptionEdit->text()); } } void FovEditorDialog::slotSaveImage() { if (m_CurrentIndex >= m_ParentWizard->getFovSnapshotList()->size()) { return; } //If the filename string contains no "/" separators, assume the //user wanted to place a file in their home directory. QString url = QFileDialog::getSaveFileUrl(KStars::Instance(), i18n("Save Image"), QUrl(QDir::homePath()), "image/png image/jpeg image/gif image/x-portable-pixmap image/bmp") .url(); QUrl fileUrl; if (!url.contains(QDir::separator())) { fileUrl = QUrl::fromLocalFile(QDir::homePath() + '/' + url); } else { fileUrl = QUrl::fromLocalFile(url); } QTemporaryFile tmpfile; tmpfile.open(); QString fname; if (fileUrl.isValid()) { if (fileUrl.isLocalFile()) { fname = fileUrl.toLocalFile(); } else { fname = tmpfile.fileName(); } //Determine desired image format from filename extension QString ext = fname.mid(fname.lastIndexOf(".") + 1); // export as raster graphics const char *format = "PNG"; if (ext.toLower() == "png") { format = "PNG"; } else if (ext.toLower() == "jpg" || ext.toLower() == "jpeg") { format = "JPG"; } else if (ext.toLower() == "gif") { format = "GIF"; } else if (ext.toLower() == "pnm") { format = "PNM"; } else if (ext.toLower() == "bmp") { format = "BMP"; } else { qWarning() << i18n("Could not parse image format of %1; assuming PNG.", fname); } if (!m_ParentWizard->getFovSnapshotList()->at(m_CurrentIndex)->getPixmap().save(fname, format)) { qDebug() << "Error: Unable to save image: " << fname; } else { qDebug() << "Image saved to file: " << fname; } } if (tmpfile.fileName() == fname) { //attempt to upload image to remote location KIO::TransferJob *uploadJob = KIO::storedHttpPost(&tmpfile, fileUrl); //if(!KIO::NetAccess::upload(tmpfile.fileName(), fileUrl, this)) if (uploadJob->exec() == false) { QString message = i18n("Could not upload image to remote location: %1", fileUrl.url()); - KMessageBox::sorry(0, message, i18n("Could not upload file")); + KMessageBox::sorry(nullptr, message, i18n("Could not upload file")); } uploadJob->kill(); } } void FovEditorDialog::setupWidgets() { if (m_ParentWizard->getFovSnapshotList()->size() > 0) { m_EditorUi->imageLabel->setPixmap(m_ParentWizard->getFovSnapshotList()->first()->getPixmap()); } updateButtons(); updateDescriptions(); } void FovEditorDialog::setupConnections() { connect(m_EditorUi->previousButton, SIGNAL(clicked()), this, SLOT(slotPreviousFov())); connect(m_EditorUi->nextButton, SIGNAL(clicked()), this, SLOT(slotNextFov())); connect(m_EditorUi->recaptureButton, SIGNAL(clicked()), this, SLOT(slotCaptureAgain())); connect(m_EditorUi->deleteButton, SIGNAL(clicked()), this, SLOT(slotDelete())); connect(m_EditorUi->descriptionEdit, SIGNAL(editingFinished()), this, SLOT(slotSaveDescription())); connect(m_EditorUi->saveButton, SIGNAL(clicked()), this, SLOT(slotSaveImage())); } void FovEditorDialog::updateButtons() { m_EditorUi->previousButton->setEnabled(m_CurrentIndex > 0); m_EditorUi->nextButton->setEnabled(m_CurrentIndex < m_ParentWizard->getFovSnapshotList()->size() - 1); } void FovEditorDialog::updateDescriptions() { if (m_ParentWizard->getFovSnapshotList()->size() == 0) { m_EditorUi->imageLabel->setText("No captured field of view images."); m_EditorUi->fovInfoLabel->setText(QString()); m_EditorUi->recaptureButton->setEnabled(false); m_EditorUi->deleteButton->setEnabled(false); m_EditorUi->descriptionEdit->setEnabled(false); m_EditorUi->saveButton->setEnabled(false); } else { FOV *fov = m_ParentWizard->getFovSnapshotList()->at(m_CurrentIndex)->getFov(); QString fovDescription = i18n("FOV (%1/%2): %3 (%4' x %5')", QString::number(m_CurrentIndex + 1), QString::number(m_ParentWizard->getFovSnapshotList()->size()), fov->name(), QString::number(fov->sizeX()), QString::number(fov->sizeY())); m_EditorUi->fovInfoLabel->setText(fovDescription); m_EditorUi->descriptionEdit->setText( m_ParentWizard->getFovSnapshotList()->at(m_CurrentIndex)->getDescription()); } } void FovEditorDialog::updateFovImage() { if (m_CurrentIndex < m_ParentWizard->getFovSnapshotList()->size()) { m_EditorUi->imageLabel->setPixmap(m_ParentWizard->getFovSnapshotList()->at(m_CurrentIndex)->getPixmap()); } } diff --git a/kstars/printing/foveditordialog.h b/kstars/printing/foveditordialog.h index 641267e3f..19df20020 100644 --- a/kstars/printing/foveditordialog.h +++ b/kstars/printing/foveditordialog.h @@ -1,117 +1,117 @@ /*************************************************************************** foveditordialog.h - K Desktop Planetarium ------------------- begin : Fri Aug 12 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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 "ui_foveditordialog.h" #include #include class PrintingWizard; /** * \class FovEditorDialogUI * \brief User interface for FOV Editor Dialog. * \author Rafał Kułaga */ class FovEditorDialogUI : public QFrame, public Ui::FovEditorDialog { Q_OBJECT public: /** * \brief Constructor. */ - explicit FovEditorDialogUI(QWidget *parent = 0); + explicit FovEditorDialogUI(QWidget *parent = nullptr); }; /** * \class FovEditorDialog * \brief Class representing FOV Editor Dialog which enables user to edit FOV snapshots. * \author Rafał Kułaga */ class FovEditorDialog : public QDialog { Q_OBJECT public: /** * \brief Constructor. */ - explicit FovEditorDialog(PrintingWizard *wizard, QWidget *parent = 0); + explicit FovEditorDialog(PrintingWizard *wizard, QWidget *parent = nullptr); private slots: /** * \brief Slot: switch to next FOV snapshot. */ void slotNextFov(); /** * \brief Slot: switch to previous FOV snapshot. */ void slotPreviousFov(); /** * \brief Slot: recapture current FOV snapshot. */ void slotCaptureAgain(); /** * \brief Slot: delete current FOV snapshot. */ void slotDelete(); /** * \brief Slot: save description of the current FOV snapshot. */ void slotSaveDescription(); /** * \brief Slot: open "Save file" dialog to choose file name and format to save image. */ void slotSaveImage(); private: /** * \brief Setup widget properties. */ void setupWidgets(); /** * \brief Setup signal-slot connections. */ void setupConnections(); /** * \brief Update buttons. */ void updateButtons(); /** * \brief Update image description. */ void updateDescriptions(); /** * \brief Update FOV image. */ void updateFovImage(); PrintingWizard *m_ParentWizard { nullptr }; FovEditorDialogUI *m_EditorUi { nullptr }; int m_CurrentIndex { 0 }; }; diff --git a/kstars/printing/legend.cpp b/kstars/printing/legend.cpp index 7180941b6..6accf24e6 100644 --- a/kstars/printing/legend.cpp +++ b/kstars/printing/legend.cpp @@ -1,596 +1,596 @@ /*************************************************************************** legend.cpp - K Desktop Planetarium ------------------- begin : Thu May 26 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #include "legend.h" #include "colorscheme.h" #include "kstarsdata.h" #include "skymap.h" #include "skyqpainter.h" #include "Options.h" #include namespace { const int symbolSize = 15; const int bRectWidth = 100; const int bRectHeight = 45; const int maxHScalePixels = 200; const int maxVScalePixels = 100; const int xSymbolSpacing = 100; const int ySymbolSpacing = 70; } Legend::Legend(LEGEND_ORIENTATION orientation, LEGEND_POSITION pos) : m_SkyMap(SkyMap::Instance()), m_Orientation(orientation), m_Position(pos), m_PositionFloating(QPoint(0, 0)), m_cScheme(KStarsData::Instance()->colorScheme()), m_SymbolSize(symbolSize), m_BRectWidth(bRectWidth), m_BRectHeight(bRectHeight), m_MaxHScalePixels(maxHScalePixels), m_MaxVScalePixels(maxVScalePixels), m_XSymbolSpacing(xSymbolSpacing), m_YSymbolSpacing(ySymbolSpacing) { m_BgColor = m_cScheme->colorNamed("SkyColor"); } Legend::~Legend() { if (m_Painter != nullptr && m_DeletePainter) { delete m_Painter; } } QSize Legend::calculateSize() { int width = 0; int height = 0; switch (m_Orientation) { case LO_HORIZONTAL: { switch (m_Type) { case LT_SCALE_ONLY: { width = 40 + m_MaxHScalePixels; height = 60; break; } case LT_MAGNITUDES_ONLY: { width = 140; height = 70; break; } case LT_SYMBOLS_ONLY: { width = 7 * m_XSymbolSpacing; height = 20 + m_SymbolSize + m_BRectHeight; break; } case LT_SCALE_MAGNITUDES: { width = 160 + m_MaxHScalePixels; height = 70; break; } case LT_FULL: { width = 7 * m_XSymbolSpacing; height = 20 + m_SymbolSize + m_BRectHeight + 70; break; } default: break; // should never happen } } break; case LO_VERTICAL: { switch (m_Type) { case LT_SCALE_ONLY: { width = 120; height = 40 + m_MaxVScalePixels; break; } case LT_MAGNITUDES_ONLY: { width = 140; height = 70; break; } case LT_SYMBOLS_ONLY: { width = 120; height = 7 * m_YSymbolSpacing; break; } case LT_SCALE_MAGNITUDES: { width = 120; height = 100 + m_MaxVScalePixels; break; } case LT_FULL: { width = 120; height = 100 + 7 * m_YSymbolSpacing + m_MaxVScalePixels; break; } default: break; // should never happen } break; } default: { return QSize(); } } return QSize(width, height); } void Legend::paintLegend(QPaintDevice *pd) { if (m_Painter) { delete m_Painter; } m_Painter = new SkyQPainter(m_SkyMap, pd); m_DeletePainter = true; m_Painter->begin(); paintLegend(m_Painter); m_Painter->end(); } void Legend::paintLegend(SkyQPainter *painter) { if (!m_DeletePainter) { m_Painter = painter; } if (m_Position != LP_FLOATING) { m_PositionFloating = positionToDeviceCoord(painter->device()); } m_Painter->translate(m_PositionFloating.x(), m_PositionFloating.y()); m_Painter->setFont(m_Font); QBrush backgroundBrush(m_BgColor, Qt::SolidPattern); QPen backgroundPen(m_cScheme->colorNamed("SNameColor")); backgroundPen.setStyle(Qt::SolidLine); // set brush & pen m_Painter->setBrush(backgroundBrush); m_Painter->setPen(backgroundPen); QSize size = calculateSize(); if (m_DrawFrame) { m_Painter->drawRect(1, 1, size.width(), size.height()); } else { QPen noLinePen; noLinePen.setStyle(Qt::NoPen); m_Painter->setPen(noLinePen); m_Painter->drawRect(1, 1, size.width(), size.height()); m_Painter->setPen(backgroundPen); } switch (m_Orientation) { case LO_HORIZONTAL: { switch (m_Type) { case LT_SCALE_ONLY: { paintScale(QPointF(20, 20)); break; } case LT_MAGNITUDES_ONLY: { paintMagnitudes(QPointF(20, 20)); break; } case LT_SYMBOLS_ONLY: { paintSymbols(QPointF(20, 20)); break; } case LT_SCALE_MAGNITUDES: { paintMagnitudes(QPointF(20, 20)); paintScale(QPointF(150, 20)); break; } case LT_FULL: { paintSymbols(QPointF(20, 20)); paintMagnitudes(QPointF(10, 40 + m_SymbolSize + m_BRectHeight)); paintScale(QPointF(200, 40 + m_SymbolSize + m_BRectHeight)); break; } default: break; // should never happen } break; } case LO_VERTICAL: { switch (m_Type) { case LT_SCALE_ONLY: { paintScale(QPointF(20, 20)); break; } case LT_MAGNITUDES_ONLY: { paintMagnitudes(QPointF(20, 20)); break; } case LT_SYMBOLS_ONLY: { paintSymbols(QPointF(20, 20)); break; } case LT_SCALE_MAGNITUDES: { paintMagnitudes(QPointF(7, 20)); paintScale(QPointF(20, 80)); break; } case LT_FULL: { paintSymbols(QPointF(30, 20)); paintMagnitudes(QPointF(7, 30 + 7 * m_YSymbolSpacing)); paintScale(QPointF(20, 90 + 7 * m_YSymbolSpacing)); break; } default: break; // should never happen } break; } default: break; // should never happen } } void Legend::paintLegend(QPaintDevice *pd, LEGEND_TYPE type, LEGEND_POSITION pos) { LEGEND_TYPE prevType = m_Type; LEGEND_POSITION prevPos = m_Position; m_Type = type; m_Position = pos; paintLegend(pd); m_Type = prevType; m_Position = prevPos; } void Legend::paintLegend(SkyQPainter *painter, LEGEND_TYPE type, LEGEND_POSITION pos) { LEGEND_TYPE prevType = m_Type; LEGEND_POSITION prevPos = m_Position; m_Type = type; m_Position = pos; paintLegend(painter); m_Type = prevType; m_Position = prevPos; } void Legend::paintSymbols(QPointF pos) { qreal x = pos.x(); qreal y = pos.y(); x += 30; switch (m_Orientation) { case Legend::LO_HORIZONTAL: { // paint Open Cluster/Asterism symbol QString label1 = i18n("Open Cluster") + '\n' + i18n("Asterism"); paintSymbol(QPointF(x, y), 3, 1, 0, label1); x += m_XSymbolSpacing; // paint Globular Cluster symbol paintSymbol(QPointF(x, y), 4, 1, 0, i18n("Globular Cluster")); x += m_XSymbolSpacing; // paint Gaseous Nebula/Dark Nebula symbol QString label3 = i18n("Gaseous Nebula") + '\n' + i18n("Dark Nebula"); paintSymbol(QPointF(x, y), 5, 1, 0, label3); x += m_XSymbolSpacing; // paint Planetary Nebula symbol paintSymbol(QPointF(x, y), 6, 1, 0, i18n("Planetary Nebula")); x += m_XSymbolSpacing; // paint Supernova Remnant paintSymbol(QPointF(x, y), 7, 1, 0, i18n("Supernova Remnant")); x += m_XSymbolSpacing; // paint Galaxy/Quasar QString label6 = i18n("Galaxy") + '\n' + i18n("Quasar"); paintSymbol(QPointF(x, y), 8, 0.5, 60, label6); x += m_XSymbolSpacing; // paint Galaxy Cluster paintSymbol(QPointF(x, y), 14, 1, 0, i18n("Galactic Cluster")); break; } case Legend::LO_VERTICAL: { // paint Open Cluster/Asterism symbol QString label1 = i18n("Open Cluster") + '\n' + i18n("Asterism"); paintSymbol(QPointF(x, y), 3, 1, 0, label1); y += m_YSymbolSpacing; // paint Globular Cluster symbol paintSymbol(QPointF(x, y), 4, 1, 0, i18n("Globular Cluster")); y += m_YSymbolSpacing; // paint Gaseous Nebula/Dark Nebula symbol QString label3 = i18n("Gaseous Nebula") + '\n' + i18n("Dark Nebula"); paintSymbol(QPointF(x, y), 5, 1, 0, label3); y += m_YSymbolSpacing; // paint Planetary Nebula symbol paintSymbol(QPointF(x, y), 6, 1, 0, i18n("Planetary Nebula")); y += m_YSymbolSpacing; // paint Supernova Remnant paintSymbol(QPointF(x, y), 7, 1, 0, i18n("Supernova Remnant")); y += m_YSymbolSpacing; // paint Galaxy/Quasar QString label6 = i18n("Galaxy") + '\n' + i18n("Quasar"); paintSymbol(QPointF(x, y), 8, 0.5, 60, label6); y += m_YSymbolSpacing; // paint Galaxy Cluster paintSymbol(QPointF(x, y), 14, 1, 0, i18n("Galactic Cluster")); break; } default: return; // should never happen } } void Legend::paintSymbol(QPointF pos, int type, float e, float angle, QString label) { qreal x = pos.x(); qreal y = pos.y(); qreal bRectHalfWidth = (qreal)m_BRectWidth / 2; // paint symbol m_Painter->drawDeepSkySymbol(pos, type, m_SymbolSize, e, angle); QRectF bRect(QPoint(x - bRectHalfWidth, y + m_SymbolSize), QPoint(x + bRectHalfWidth, y + m_SymbolSize + m_BRectHeight)); //m_Painter->drawRect(bRect); // paint label m_Painter->drawText(bRect, label, QTextOption(Qt::AlignHCenter)); } void Legend::paintMagnitudes(QPointF pos) { qreal x = pos.x(); qreal y = pos.y(); m_Painter->drawText(x, y, i18n("Star Magnitudes:")); y += 15; for (int i = 1; i <= 9; i += 2) { m_Painter->drawPointSource(QPointF(x + i * 10, y), m_Painter->starWidth(i)); m_Painter->drawText(x + i * 10 - 4, y + 20, QString::number(i)); } } void Legend::paintScale(QPointF pos) { qreal maxScalePixels; switch (m_Orientation) { case LO_HORIZONTAL: { maxScalePixels = m_MaxHScalePixels; break; } case LO_VERTICAL: { maxScalePixels = m_MaxVScalePixels; break; } default: return; // should never happen } qreal maxArcsec = maxScalePixels * 57.3 * 3600 / Options::zoomFactor(); int deg = 0; int arcmin = 0; int arcsec = 0; QString lab; if (maxArcsec >= 3600) { deg = maxArcsec / 3600; lab = QString::number(deg) + QString::fromWCharArray(L"\u00B0"); } else if (maxArcsec >= 60) { arcmin = maxArcsec / 60; lab = QString::number(arcmin) + '\''; } else { arcsec = maxArcsec; lab = QString::number(arcsec) + "\""; } int actualArcsec = 3600 * deg + 60 * arcmin + arcsec; qreal size = actualArcsec * Options::zoomFactor() / 57.3 / 3600; qreal x = pos.x(); qreal y = pos.y(); switch (m_Orientation) { case LO_HORIZONTAL: { m_Painter->drawText(pos, i18n("Chart Scale:")); y += 15; m_Painter->drawLine(x, y, x + size, y); // paint line endings m_Painter->drawLine(x, y - 5, x, y + 5); m_Painter->drawLine(x + size, y - 5, x + size, y + 5); // paint scale text QRectF bRect(QPoint(x, y), QPoint(x + size, y + 20)); m_Painter->drawText(bRect, lab, QTextOption(Qt::AlignHCenter)); break; } case LO_VERTICAL: { m_Painter->drawText(pos, i18n("Chart Scale:")); y += 10; x += 40; m_Painter->drawLine(x, y, x, y + size); // paint line endings m_Painter->drawLine(x - 5, y, x + 5, y); m_Painter->drawLine(x - 5, y + size, x + 5, y + size); // paint scale text QRectF bRect(QPoint(x + 5, y), QPoint(x + 20, y + size)); //m_Painter->drawRect(bRect); m_Painter->drawText(bRect, lab, QTextOption(Qt::AlignVCenter)); break; } default: return; // should never happen } } QPoint Legend::positionToDeviceCoord(QPaintDevice *pd) { QSize legendSize = calculateSize(); switch (m_Position) { case LP_UPPER_LEFT: // position: upper left corner { return QPoint(0, 0); } case LP_UPPER_RIGHT: // position: upper right corner { return QPoint(pd->width() - legendSize.width(), 0); } case LP_LOWER_LEFT: // position: lower left corner { return QPoint(0, pd->height() - legendSize.height()); } case LP_LOWER_RIGHT: // position: lower right corner { return QPoint(pd->width() - legendSize.width(), pd->height() - legendSize.height()); } default: // legend is floating { return QPoint(); } } } Legend::Legend(const Legend &o) - : m_Painter(0), m_SkyMap(o.m_SkyMap), m_DeletePainter(o.m_DeletePainter), m_Type(o.m_Type), + : m_Painter(nullptr), m_SkyMap(o.m_SkyMap), m_DeletePainter(o.m_DeletePainter), m_Type(o.m_Type), m_Orientation(o.m_Orientation), m_Position(o.m_Position), m_PositionFloating(o.m_PositionFloating), m_cScheme(o.m_cScheme), m_Font(o.m_Font), m_BgColor(o.m_BgColor), m_DrawFrame(o.m_DrawFrame), m_SymbolSize(o.m_SymbolSize), m_BRectWidth(o.m_BRectWidth), m_BRectHeight(o.m_BRectHeight), m_MaxHScalePixels(o.m_MaxHScalePixels), m_MaxVScalePixels(o.m_MaxVScalePixels), m_XSymbolSpacing(o.m_XSymbolSpacing), m_YSymbolSpacing(o.m_YSymbolSpacing) { } diff --git a/kstars/printing/printingwizard.cpp b/kstars/printing/printingwizard.cpp index 2ce7118b4..7902d67f7 100644 --- a/kstars/printing/printingwizard.cpp +++ b/kstars/printing/printingwizard.cpp @@ -1,601 +1,601 @@ /*************************************************************************** printingwizard.cpp - K Desktop Planetarium ------------------- begin : Tue Aug 2 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #include "printingwizard.h" #include #include #include #include "finderchart.h" #include "loggingform.h" #include "detailstable.h" #include "pwizobjectselection.h" #include "pwizchartconfig.h" #include "pwizfovbrowse.h" #include "pwizfovconfig.h" #include "pwizfovtypeselection.h" #include "pwizfovmanual.h" #include "pwizfovsh.h" #include "pwizchartcontents.h" #include "pwizprint.h" #include "projections/projector.h" #include "kstars.h" #include "kstarsdata.h" #include "skymap.h" #include "legend.h" #include "shfovexporter.h" #include "Options.h" #include "kspaths.h" PWizWelcomeUI::PWizWelcomeUI(QWidget *parent) : QFrame(parent) { setupUi(this); } PrintingWizard::PrintingWizard(QWidget *parent) - : QDialog(parent), m_KStars(KStars::Instance()), m_FinderChart(0), m_SkyObject(0), m_FovType(FT_UNDEFINED), - m_FovImageSize(QSize(500, 500)), m_ShBeginObject(0), m_PointingShBegin(false), m_SwitchColors(false), + : QDialog(parent), m_KStars(KStars::Instance()), m_FinderChart(nullptr), m_SkyObject(nullptr), m_FovType(FT_UNDEFINED), + m_FovImageSize(QSize(500, 500)), m_ShBeginObject(nullptr), m_PointingShBegin(false), m_SwitchColors(false), m_RecapturingFov(false), m_RecaptureIdx(-1) { m_Printer = new QPrinter(QPrinter::ScreenResolution); setupWidgets(); } PrintingWizard::~PrintingWizard() { // Clean up if (m_Printer) { delete m_Printer; } if (m_FinderChart) { delete m_FinderChart; } qDeleteAll(m_FovSnapshots); } void PrintingWizard::updateStepButtons() { switch (m_WizardStack->currentIndex()) { case PW_OBJECT_SELECTION: // object selection { nextB->setEnabled(m_SkyObject != nullptr); break; } } } void PrintingWizard::beginPointing() { // If there is sky object already selected, center sky map around it if (m_SkyObject) { m_KStars->map()->setClickedObject(m_SkyObject); m_KStars->map()->slotCenter(); } m_KStars->map()->setObjectPointingMode(true); hide(); } void PrintingWizard::beginShBeginPointing() { m_PointingShBegin = true; if (m_ShBeginObject) { m_KStars->map()->setClickedObject(m_SkyObject); m_KStars->map()->slotCenter(); } m_KStars->map()->setObjectPointingMode(true); hide(); } void PrintingWizard::pointingDone(SkyObject *obj) { if (m_PointingShBegin) { m_ShBeginObject = obj; m_WizFovShUI->setBeginObject(obj); m_PointingShBegin = false; } else { m_SkyObject = obj; m_WizObjectSelectionUI->setSkyObject(obj); } show(); } void PrintingWizard::beginFovCapture() { if (m_SkyObject) { slewAndBeginCapture(m_SkyObject); } } void PrintingWizard::beginFovCapture(SkyPoint *center, FOV *fov) { slewAndBeginCapture(center, fov); } void PrintingWizard::captureFov() { if (m_KStars->data()->getVisibleFOVs().isEmpty()) { return; } QPixmap pixmap(m_FovImageSize); m_SimpleFovExporter.exportFov(m_KStars->data()->getVisibleFOVs().first(), &pixmap); if (m_WizFovConfigUI->isLegendEnabled()) { // Set legend position, orientation and type Legend legend(m_WizFovConfigUI->getLegendOrientation(), m_WizFovConfigUI->getLegendPosition()); legend.setType(m_WizFovConfigUI->getLegendType()); // Check if alpha blending is enabled if (m_WizFovConfigUI->isAlphaBlendingEnabled()) { QColor bgColor = legend.getBgColor(); bgColor.setAlpha(200); legend.setBgColor(bgColor); } // Paint legend legend.paintLegend(&pixmap); } FovSnapshot *snapshot = new FovSnapshot(pixmap, QString(), m_KStars->data()->getVisibleFOVs().first(), m_KStars->map()->getCenterPoint()); if (m_RecapturingFov) { delete m_FovSnapshots.at(m_RecaptureIdx); m_FovSnapshots.replace(m_RecaptureIdx, snapshot); m_KStars->map()->setFovCaptureMode(false); m_RecapturingFov = false; m_RecaptureIdx = -1; fovCaptureDone(); } else { m_FovSnapshots.append(snapshot); } } void PrintingWizard::fovCaptureDone() { //Restore old color scheme if necessary //(if printing was aborted, the ColorScheme is still restored) if (m_SwitchColors) { m_KStars->loadColorScheme(m_PrevSchemeName); m_KStars->map()->forceUpdate(); } if (m_RecapturingFov) { m_RecapturingFov = false; m_RecaptureIdx = -1; } show(); } void PrintingWizard::beginShFovCapture() { if (!m_ShBeginObject) { return; } ShFovExporter exporter(this, KStars::Instance()->map()); // Get selected FOV symbol double fovArcmin(0); foreach (FOV *fov, KStarsData::Instance()->getAvailableFOVs()) { if (fov->name() == m_WizFovShUI->getFovName()) { fovArcmin = qMin(fov->sizeX(), fov->sizeY()); break; } } // Calculate path and check if it's not empty if (!exporter.calculatePath(*m_SkyObject, *m_ShBeginObject, fovArcmin / 60, m_WizFovShUI->getMaglim())) { KMessageBox::information(this, i18n("Star hopper returned empty path. We advise you to change star hopping settings " "or use manual capture mode."), i18n("Star hopper failed to find path")); return; } // If FOV shape should be overridden, do this now m_SimpleFovExporter.setFovShapeOverriden(m_WizFovConfigUI->isFovShapeOverriden()); m_SimpleFovExporter.setFovSymbolDrawn(m_WizFovConfigUI->isFovShapeOverriden()); // If color scheme should be switched, save previous scheme name and switch to "sky chart" color scheme m_SwitchColors = m_WizFovConfigUI->isSwitchColorsEnabled(); m_PrevSchemeName = m_KStars->data()->colorScheme()->fileName(); if (m_SwitchColors) { m_KStars->loadColorScheme("chart.colors"); } // Save previous FOV symbol names and switch to symbol selected by user QStringList prevFovNames = Options::fOVNames(); Options::setFOVNames(QStringList(m_WizFovShUI->getFovName())); KStarsData::Instance()->syncFOV(); if (KStarsData::Instance()->getVisibleFOVs().isEmpty()) { return; } // Hide Printing Wizard hide(); // Draw and export path exporter.exportPath(); // Restore old color scheme if necessary if (m_SwitchColors) { m_KStars->loadColorScheme(m_PrevSchemeName); m_KStars->map()->forceUpdate(); } // Update skymap m_KStars->map()->forceUpdate(true); // Restore previous FOV symbol names Options::setFOVNames(prevFovNames); KStarsData::Instance()->syncFOV(); //FIXME: this is _dirty_ workaround to get PrintingWizard displayed in its previous position. QTimer::singleShot(50, this, SLOT(show())); } void PrintingWizard::recaptureFov(int idx) { // Set recapturing flag and index of the FOV snapshot to replace m_RecapturingFov = true; m_RecaptureIdx = idx; // Begin FOV snapshot capture SkyPoint p = m_FovSnapshots.at(m_RecaptureIdx)->getCentralPoint(); slewAndBeginCapture(&p, m_FovSnapshots.at(m_RecaptureIdx)->getFov()); } void PrintingWizard::slotPrevPage() { int currentIdx = m_WizardStack->currentIndex(); switch (currentIdx) { case PW_FOV_BROWSE: { switch (m_FovType) { case FT_MANUAL: { m_WizardStack->setCurrentIndex(PW_FOV_MANUAL); break; } case FT_STARHOPPER: { m_WizardStack->setCurrentIndex(PW_FOV_SH); break; } default: { return; } } break; } case PW_FOV_SH: { m_WizardStack->setCurrentIndex(PW_FOV_CONFIG); break; } default: { m_WizardStack->setCurrentIndex(currentIdx - 1); break; } } updateStepButtons(); updateButtons(); } void PrintingWizard::slotNextPage() { int currentIdx = m_WizardStack->currentIndex(); switch (currentIdx) { case PW_FOV_TYPE: { m_FovType = m_WizFovTypeSelectionUI->getFovExportType(); m_WizardStack->setCurrentIndex(PW_FOV_CONFIG); break; } case PW_FOV_CONFIG: { switch (m_FovType) { case FT_MANUAL: { m_WizardStack->setCurrentIndex(PW_FOV_MANUAL); break; } case FT_STARHOPPER: { m_WizardStack->setCurrentIndex(PW_FOV_SH); break; } default: // Undefined FOV type - do nothing { return; } } break; } case PW_FOV_MANUAL: { m_WizardStack->setCurrentIndex(PW_FOV_BROWSE); break; } case PW_FOV_BROWSE: { m_WizChartContentsUI->entered(); m_WizardStack->setCurrentIndex(PW_CHART_CONTENTS); break; } case PW_CHART_CONTENTS: { createFinderChart(); m_WizardStack->setCurrentIndex(PW_CHART_PRINT); break; } default: { m_WizardStack->setCurrentIndex(currentIdx + 1); } } updateButtons(); updateStepButtons(); } void PrintingWizard::setupWidgets() { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif m_WizardStack = new QStackedWidget(this); setWindowTitle(i18n("Printing Wizard")); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(m_WizardStack); setLayout(mainLayout); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); nextB = new QPushButton(i18n("&Next >")); nextB->setToolTip(i18n("Go to next Wizard page")); nextB->setDefault(true); backB = new QPushButton(i18n("< &Back")); backB->setToolTip(i18n("Go to previous Wizard page")); backB->setEnabled(false); buttonBox->addButton(backB, QDialogButtonBox::ActionRole); buttonBox->addButton(nextB, QDialogButtonBox::ActionRole); connect(nextB, SIGNAL(clicked()), this, SLOT(slotNextPage())); connect(backB, SIGNAL(clicked()), this, SLOT(slotPrevPage())); // Create step widgets m_WizWelcomeUI = new PWizWelcomeUI(m_WizardStack); m_WizObjectSelectionUI = new PWizObjectSelectionUI(this, m_WizardStack); m_WizChartConfigUI = new PWizChartConfigUI(this); m_WizFovTypeSelectionUI = new PWizFovTypeSelectionUI(this, m_WizardStack); m_WizFovConfigUI = new PWizFovConfigUI(m_WizardStack); m_WizFovManualUI = new PWizFovManualUI(this, m_WizardStack); m_WizFovShUI = new PWizFovShUI(this, m_WizardStack); m_WizFovBrowseUI = new PWizFovBrowseUI(this, m_WizardStack); m_WizChartContentsUI = new PWizChartContentsUI(this, m_WizardStack); m_WizPrintUI = new PWizPrintUI(this, m_WizardStack); // Add step widgets to m_WizardStack m_WizardStack->addWidget(m_WizWelcomeUI); m_WizardStack->addWidget(m_WizObjectSelectionUI); m_WizardStack->addWidget(m_WizChartConfigUI); m_WizardStack->addWidget(m_WizFovTypeSelectionUI); m_WizardStack->addWidget(m_WizFovConfigUI); m_WizardStack->addWidget(m_WizFovManualUI); m_WizardStack->addWidget(m_WizFovShUI); m_WizardStack->addWidget(m_WizFovBrowseUI); m_WizardStack->addWidget(m_WizChartContentsUI); m_WizardStack->addWidget(m_WizPrintUI); // Set banner images for steps QPixmap bannerImg; if (bannerImg.load(KSPaths::locate(QStandardPaths::GenericDataLocation, "wzstars.png"))) { m_WizWelcomeUI->banner->setPixmap(bannerImg); m_WizObjectSelectionUI->banner->setPixmap(bannerImg); m_WizChartConfigUI->banner->setPixmap(bannerImg); m_WizFovTypeSelectionUI->banner->setPixmap(bannerImg); m_WizFovConfigUI->banner->setPixmap(bannerImg); m_WizFovManualUI->banner->setPixmap(bannerImg); m_WizFovShUI->banner->setPixmap(bannerImg); m_WizFovBrowseUI->banner->setPixmap(bannerImg); m_WizChartContentsUI->banner->setPixmap(bannerImg); m_WizPrintUI->banner->setPixmap(bannerImg); } backB->setEnabled(false); } void PrintingWizard::updateButtons() { nextB->setEnabled(m_WizardStack->currentIndex() < m_WizardStack->count() - 1); backB->setEnabled(m_WizardStack->currentIndex() > 0); } void PrintingWizard::slewAndBeginCapture(SkyPoint *center, FOV *fov) { if (!center) { return; } // If pointer to FOV is passed... if (fov) { // Switch to appropriate FOV symbol Options::setFOVNames(QStringList(fov->name())); m_KStars->data()->syncFOV(); // Adjust map's zoom level double zoom = m_FovImageSize.width() > m_FovImageSize.height() ? SimpleFovExporter::calculateZoomLevel(m_FovImageSize.width(), fov->sizeX()) : SimpleFovExporter::calculateZoomLevel(m_FovImageSize.height(), fov->sizeY()); m_KStars->map()->setZoomFactor(zoom); } m_SimpleFovExporter.setFovShapeOverriden(m_WizFovConfigUI->isFovShapeOverriden()); m_SimpleFovExporter.setFovSymbolDrawn(m_WizFovConfigUI->isFovShapeOverriden()); m_SwitchColors = m_WizFovConfigUI->isSwitchColorsEnabled(); m_PrevSchemeName = m_KStars->data()->colorScheme()->fileName(); if (m_SwitchColors) { m_KStars->loadColorScheme("chart.colors"); } m_KStars->hideAllFovExceptFirst(); m_KStars->map()->setClickedPoint(center); m_KStars->map()->slotCenter(); m_KStars->map()->setFovCaptureMode(true); hide(); } void PrintingWizard::createFinderChart() { // Delete old (if needed) and create new FinderChart if (m_FinderChart) { delete m_FinderChart; } m_FinderChart = new FinderChart; // Insert title and subtitle m_FinderChart->insertTitleSubtitle(m_WizChartConfigUI->titleEdit->text(), m_WizChartConfigUI->subtitleEdit->text()); // Insert description if (!m_WizChartConfigUI->descriptionTextEdit->toPlainText().isEmpty()) { m_FinderChart->insertDescription(m_WizChartConfigUI->descriptionTextEdit->toPlainText()); } // Insert simple finder chart logging form if (m_WizChartContentsUI->isLoggingFormChecked()) { LoggingForm chartLogger; chartLogger.createFinderChartLogger(); m_FinderChart->insertSectionTitle(i18n("Logging Form")); m_FinderChart->insertLoggingForm(&chartLogger); } m_FinderChart->insertSectionTitle(i18n("Field of View Snapshots")); // Insert FOV images and descriptions for (int i = 0; i < m_FovSnapshots.size(); i++) { FOV *fov = m_FovSnapshots.at(i)->getFov(); QString fovDescription = i18nc("%1 = FOV index, %2 = FOV count, %3 = FOV name, %4 = FOV X size, %5 = FOV Y size", "FOV (%1/%2): %3 (%4' x %5')", QString::number(i + 1), QString::number(m_FovSnapshots.size()), fov->name(), QString::number(fov->sizeX()), QString::number(fov->sizeY())) + "\n"; m_FinderChart->insertImage(m_FovSnapshots.at(i)->getPixmap().toImage(), fovDescription + m_FovSnapshots.at(i)->getDescription(), true); } if (m_WizChartContentsUI->isGeneralTableChecked() || m_WizChartContentsUI->isPositionTableChecked() || m_WizChartContentsUI->isRSTTableChecked() || m_WizChartContentsUI->isAstComTableChecked()) { m_FinderChart->insertSectionTitle(i18n("Details About Object")); m_FinderChart->insertGeoTimeInfo(KStarsData::Instance()->ut(), KStarsData::Instance()->geo()); } // Insert details table : general DetailsTable detTable; if (m_WizChartContentsUI->isGeneralTableChecked()) { detTable.createGeneralTable(m_SkyObject); m_FinderChart->insertDetailsTable(&detTable); } // Insert details table : position if (m_WizChartContentsUI->isPositionTableChecked()) { detTable.createCoordinatesTable(m_SkyObject, m_KStars->data()->ut(), m_KStars->data()->geo()); m_FinderChart->insertDetailsTable(&detTable); } // Insert details table : RST if (m_WizChartContentsUI->isRSTTableChecked()) { detTable.createRSTTAble(m_SkyObject, m_KStars->data()->ut(), m_KStars->data()->geo()); m_FinderChart->insertDetailsTable(&detTable); } // Insert details table : Asteroid/Comet if (m_WizChartContentsUI->isAstComTableChecked()) { detTable.createAsteroidCometTable(m_SkyObject); m_FinderChart->insertDetailsTable(&detTable); } } diff --git a/kstars/printing/printingwizard.h b/kstars/printing/printingwizard.h index 913b1338e..c872f8c69 100644 --- a/kstars/printing/printingwizard.h +++ b/kstars/printing/printingwizard.h @@ -1,287 +1,287 @@ /*************************************************************************** printingwizard.h - K Desktop Planetarium ------------------- begin : Tue Aug 2 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #ifndef PRINTINGWIZARD_H #define PRINTINGWIZARD_H #include "ui_pwizwelcome.h" #include "QDialog" #include "simplefovexporter.h" #include "fovsnapshot.h" #include "QSize" class KStars; class PWizObjectSelectionUI; class PWizFovBrowseUI; class PWizFovTypeSelectionUI; class PWizFovConfigUI; class PWizFovManualUI; class PWizFovShUI; class PWizChartConfigUI; class PWizChartContentsUI; class PWizPrintUI; class FinderChart; class SkyObject; class QStackedWidget; class QPrinter; /** * \class PWizWelcomeUI * \brief User interface for the first step of the Printing Wizard. * \author Rafał Kułaga */ class PWizWelcomeUI : public QFrame, public Ui::PWizWelcome { Q_OBJECT public: /** * \brief Constructor. */ - explicit PWizWelcomeUI(QWidget *parent = 0); + explicit PWizWelcomeUI(QWidget *parent = nullptr); }; /** * \class PrintingWizard * \brief Class representing Printing Wizard for KStars printed documents (currently only finder charts). * \author Rafał Kułaga */ class PrintingWizard : public QDialog { Q_OBJECT public: /** * \brief Wizard steps enumeration. */ enum WIZARD_STEPS { PW_WELCOME = 0, PW_OBJECT_SELECTION = 1, PW_CHART_CONFIG = 2, PW_FOV_TYPE = 3, PW_FOV_CONFIG = 4, PW_FOV_MANUAL = 5, PW_FOV_SH = 6, PW_FOV_BROWSE = 7, PW_CHART_CONTENTS = 8, PW_CHART_PRINT = 9 }; /** * \brief FOV export method type enumeration. */ enum FOV_TYPE { FT_MANUAL, FT_STARHOPPER, FT_UNDEFINED }; /** * \brief Constructor. */ - explicit PrintingWizard(QWidget *parent = 0); + explicit PrintingWizard(QWidget *parent = nullptr); /** * \brief Destructor. */ ~PrintingWizard() override; /** * \brief Get used FOV export method. * \return Selected FOV export method. */ FOV_TYPE getFovType() { return m_FovType; } /** * \brief Get printer used by Printing Wizard. * \return Used printer. */ QPrinter *getPrinter() { return m_Printer; } /** * \brief Get used FinderChart document. * \return Used FinderChart document. */ FinderChart *getFinderChart() { return m_FinderChart; } /** * \brief Get selected SkyObject, for which FinderChart is created. * \return Selected SkyObject. */ SkyObject *getSkyObject() { return m_SkyObject; } /** * \brief Get FovSnapshot list. * \return Used FovSnapshot list. */ QList *getFovSnapshotList() { return &m_FovSnapshots; } /** * \brief Get FOV snapshot image size. * \return Size of the FOV snapshot image. */ QSize getFovImageSize() { return m_FovImageSize; } /** * \brief Get pointer to the SimpleFovExporter class instance. * \return Pointer to the SimpleFovExporter instance used by Printing Wizard. */ SimpleFovExporter *getFovExporter() { return &m_SimpleFovExporter; } /** * \brief Get object at which star hopping will begin. * \return Source object for star hopper. */ SkyObject *getShBeginObject() { return m_ShBeginObject; } /** * \brief Set SkyObject for which FinderChart is created. * \return SkyObject for which finder chart is created. */ void setSkyObject(SkyObject *obj) { m_SkyObject = obj; } /** * \brief Set SkyObject at which star hopper will begin. * \return SkyObject at which star hopper will begin. */ void setShBeginObject(SkyObject *obj) { m_ShBeginObject = obj; } /** * \brief Update Next/Previous step buttons. */ void updateStepButtons(); /** * \brief Set SkyMap to pointing mode and hide Printing Wizard. */ void beginPointing(); /** * \brief Enter star hopping begin pointing mode. */ void beginShBeginPointing(); /** * \brief Quit object pointing mode and set the pointed object. * \param obj Pointer to the SkyObject that was pointed on SkyMap. */ void pointingDone(SkyObject *obj); /** * \brief Hide Printing Wizard and put SkyMap in FOV capture mode. */ void beginFovCapture(); /** * \brief Hide Printing Wizard and put SkyMap in FOV capture mode. * \param center Point at which SkyMap should be centered. * \param fov Field of view symbol, used to calculate zoom factor. */ - void beginFovCapture(SkyPoint *center, FOV *fov = 0); + void beginFovCapture(SkyPoint *center, FOV *fov = nullptr); /** * \brief Capture current contents of FOV symbol. */ void captureFov(); /** * \brief Disable FOV capture mode. */ void fovCaptureDone(); /** * \brief Capture FOV snapshots using star hopper-based method. */ void beginShFovCapture(); /** * \brief Recapture FOV snapshot of passed index. * \param idx Index of the element to be recaptured. */ void recaptureFov(int idx); private slots: /** * \brief Slot: go to the previous page of Printing Wizard. */ void slotPrevPage(); /** * \brief Slot: go to the next page of Printing Wizard. */ void slotNextPage(); private: /** * \brief Setup widget properties. */ void setupWidgets(); /** * \brief Update Previous/Next buttons. */ void updateButtons(); /** * \brief Private method, used to center SkyMap around passed SkyPoint, and enter FOV capture mode. * \param center Point at which SkyMap should be centered. * \param fov Field of view symbol, used to calculate zoom factor. */ - void slewAndBeginCapture(SkyPoint *center, FOV *fov = 0); + void slewAndBeginCapture(SkyPoint *center, FOV *fov = nullptr); /** * \brief Create finder chart using settings from all steps. */ void createFinderChart(); KStars *m_KStars; FinderChart *m_FinderChart; SkyObject *m_SkyObject; QStackedWidget *m_WizardStack; QPrinter *m_Printer; FOV_TYPE m_FovType; QSize m_FovImageSize; SimpleFovExporter m_SimpleFovExporter; QList m_FovSnapshots; SkyObject *m_ShBeginObject; bool m_PointingShBegin; bool m_SwitchColors; QString m_PrevSchemeName; bool m_RecapturingFov; int m_RecaptureIdx; QPushButton *nextB, *backB; PWizWelcomeUI *m_WizWelcomeUI; PWizObjectSelectionUI *m_WizObjectSelectionUI; PWizFovTypeSelectionUI *m_WizFovTypeSelectionUI; PWizFovConfigUI *m_WizFovConfigUI; PWizFovManualUI *m_WizFovManualUI; PWizFovShUI *m_WizFovShUI; PWizFovBrowseUI *m_WizFovBrowseUI; PWizChartConfigUI *m_WizChartConfigUI; PWizChartContentsUI *m_WizChartContentsUI; PWizPrintUI *m_WizPrintUI; }; #endif // PRINTINGWIZARD_H diff --git a/kstars/printing/pwizchartconfig.h b/kstars/printing/pwizchartconfig.h index 215e7353e..f0d16623b 100644 --- a/kstars/printing/pwizchartconfig.h +++ b/kstars/printing/pwizchartconfig.h @@ -1,56 +1,56 @@ /*************************************************************************** pwizchartconfig.h - K Desktop Planetarium ------------------- begin : Wed Aug 10 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #ifndef PWIZCHARTCONFIG_H #define PWIZCHARTCONFIG_H #include "ui_pwizchartconfig.h" /** * \class PWizChartConfigUI * \brief User interface for "Configure basic finder chart settings" step of the Printing Wizard. * \author Rafał Kułaga */ class PWizChartConfigUI : public QFrame, public Ui::PWizChartConfig { Q_OBJECT public: /** * \brief Constructor. */ - explicit PWizChartConfigUI(QWidget *parent = 0); + explicit PWizChartConfigUI(QWidget *parent = nullptr); /** * \brief Get entered chart title. * \return Chart title. */ QString getChartTitle() { return titleEdit->text(); } /** * \brief Get entered chart subtitle. * \return Chart subtitle. */ QString getChartSubtitle() { return subtitleEdit->text(); } /** * \brief Get entered chart description. * \return Chart description. */ QString getChartDescription() { return descriptionTextEdit->toPlainText(); } }; #endif // PWIZCHARTCONFIG_H diff --git a/kstars/printing/pwizchartcontents.h b/kstars/printing/pwizchartcontents.h index 891d2ad93..5da8b8c76 100644 --- a/kstars/printing/pwizchartcontents.h +++ b/kstars/printing/pwizchartcontents.h @@ -1,78 +1,78 @@ /*************************************************************************** pwizchartcontents.h - K Desktop Planetarium ------------------- begin : Sun Aug 7 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #ifndef PWIZCHARTCONTENTS_H #define PWIZCHARTCONTENTS_H #include "ui_pwizchartcontents.h" class PrintingWizard; /** * \class PWizChartContentsUI * \brief User interface for "Configure chart contents" step of the Printing Wizard. * \author Rafał Kułaga */ class PWizChartContentsUI : public QFrame, public Ui::PWizChartContents { Q_OBJECT public: /** * \brief Constructor. */ - explicit PWizChartContentsUI(PrintingWizard *wizard, QWidget *parent = 0); + explicit PWizChartContentsUI(PrintingWizard *wizard, QWidget *parent = nullptr); /** * \brief Enable or disable specific fields depending on the type of selected object. */ void entered(); /** * \brief Check if general details table is enabled. * \return True if general details table is enabled. */ bool isGeneralTableChecked(); /** * \brief Check if position details table is enabled. * \return True if position details table is enabled. */ bool isPositionTableChecked(); /** * \brief Check if Rise/Set/Transit details table is enabled. * \return True if Rise/Set/Transit details table is enabled. */ bool isRSTTableChecked(); /** * \brief Check if Asteroid/Comet details table is enabled. * \return True if Asteroid/Comet details table is enabled. */ bool isAstComTableChecked(); /** * \brief Check if logging form is enabled. * \return True if logging form is enabled. */ bool isLoggingFormChecked(); private: PrintingWizard *m_ParentWizard; }; #endif // PWIZCHARTCONTENTS_H diff --git a/kstars/printing/pwizfovbrowse.h b/kstars/printing/pwizfovbrowse.h index d2765e06e..5c94ed2ea 100644 --- a/kstars/printing/pwizfovbrowse.h +++ b/kstars/printing/pwizfovbrowse.h @@ -1,49 +1,49 @@ /*************************************************************************** pwizfovbrowse.h - K Desktop Planetarium ------------------- begin : Fri Aug 12 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #ifndef PWIZFOVBROWSER_H #define PWIZFOVBROWSER_H #include "ui_pwizfovbrowse.h" class PrintingWizard; /** * \class PWizFovBrowseUI * \brief User interface for "Browse captured FOV images" step of Printing Wizard. * \author Rafał Kułaga */ class PWizFovBrowseUI : public QFrame, public Ui::PWizFovBrowse { Q_OBJECT public: /** * \brief Constructor. */ - explicit PWizFovBrowseUI(PrintingWizard *wizard, QWidget *parent = 0); + explicit PWizFovBrowseUI(PrintingWizard *wizard, QWidget *parent = nullptr); private slots: /** * \brief Slot: open FOV editor window. */ void slotOpenFovEditor(); private: PrintingWizard *m_ParentWizard; }; #endif // PWIZFOVBROWSER_H diff --git a/kstars/printing/pwizfovconfig.h b/kstars/printing/pwizfovconfig.h index 0c2d2d7ea..c3bd8b5ab 100644 --- a/kstars/printing/pwizfovconfig.h +++ b/kstars/printing/pwizfovconfig.h @@ -1,105 +1,105 @@ /*************************************************************************** pwizfovconfig.h - K Desktop Planetarium ------------------- begin : Sun Aug 14 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #ifndef PWIZFOVCONFIG_H #define PWIZFOVCONFIG_H #include "ui_pwizfovconfig.h" #include "legend.h" /** * \class PWizFovConfigUI * \brief User interface for "Configure common FOV export options" step of the Printing Wizard. * \author Rafał Kułaga */ class PWizFovConfigUI : public QFrame, public Ui::PWizFovConfig { Q_OBJECT public: /** * \brief Constructor. */ - explicit PWizFovConfigUI(QWidget *parent = 0); + explicit PWizFovConfigUI(QWidget *parent = nullptr); /** * \brief Check if switching to "Sky Chart" color scheme is enabled. * \return True if color scheme switching is enabled. */ bool isSwitchColorsEnabled() { return switchColorsBox->isChecked(); } /** * \brief Check if FOV shape is always rectangular. * \return True if FOV shape is always rectangular. */ bool isFovShapeOverriden() { return overrideShapeBox->isChecked(); } /** * \brief Check if legend will be added to FOV images. * \return True if legend will be added to FOV images. */ bool isLegendEnabled() { return addLegendBox->isChecked(); } /** * \brief Check if alpha blending is enabled. * \return True if alpha blending is enabled. */ bool isAlphaBlendingEnabled() { return useAlphaBlendBox->isChecked(); } /** * \brief Get selected legend type. * \return Selected legend type. */ Legend::LEGEND_TYPE getLegendType(); /** * \brief Get selected legend orientation. * \return Selected legend orientation. */ Legend::LEGEND_ORIENTATION getLegendOrientation() { return static_cast(orientationCombo->currentIndex()); } /** * \brief Get selected legend position. * \return Selected legend position. */ Legend::LEGEND_POSITION getLegendPosition() { return static_cast(positionCombo->currentIndex()); } private slots: /** * \brief Slot: enable or disable legend configuration fields. * \param enabled True if legend configuration fields should be enabled. */ void slotUpdateLegendFields(bool enabled); private: /** * \brief Configure widgets. */ void setupWidgets(); /** * \brief Configure signal-slot connections. */ void setupConnections(); }; #endif // PWIZFOVCONFIG_H diff --git a/kstars/printing/pwizfovmanual.h b/kstars/printing/pwizfovmanual.h index ef845d4e6..f2f13d8ba 100644 --- a/kstars/printing/pwizfovmanual.h +++ b/kstars/printing/pwizfovmanual.h @@ -1,49 +1,49 @@ /*************************************************************************** pwizfovmanual.h - K Desktop Planetarium ------------------- begin : Sun Aug 7 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #ifndef PWIZFOVMANUAL_H #define PWIZFOVMANUAL_H #include "ui_pwizfovmanual.h" class PrintingWizard; /** * \class PWizFovManualUI * \brief User interface for "Manual FOV capture" step of the Printing Wizard. * \author Rafał Kułaga */ class PWizFovManualUI : public QFrame, public Ui::PWizFovManual { Q_OBJECT public: /** * \brief Constructor. */ - explicit PWizFovManualUI(PrintingWizard *wizard, QWidget *parent = 0); + explicit PWizFovManualUI(PrintingWizard *wizard, QWidget *parent = nullptr); private slots: /** * \brief Slot: enter manual FOV capture mode. */ void slotExportFov(); private: PrintingWizard *m_ParentWizard; }; #endif // PWIZFOVMANUAL_H diff --git a/kstars/printing/pwizfovsh.h b/kstars/printing/pwizfovsh.h index 955c2a77e..e9daefa04 100644 --- a/kstars/printing/pwizfovsh.h +++ b/kstars/printing/pwizfovsh.h @@ -1,93 +1,93 @@ /*************************************************************************** pwizfovsh.h - K Desktop Planetarium ------------------- begin : Mon Aug 15 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #ifndef PWIZFOVSH_H #define PWIZFOVSH_H #include "ui_pwizfovsh.h" class PrintingWizard; class SkyObject; /** * \class PWizFovShUI * \brief User interface for "Star hopper FOV snapshot capture" step of the Printing Wizard. * \author Rafał Kułaga */ class PWizFovShUI : public QFrame, public Ui::PWizFovSh { Q_OBJECT public: /** * \brief Constructor. */ - explicit PWizFovShUI(PrintingWizard *wizard, QWidget *parent = 0); + explicit PWizFovShUI(PrintingWizard *wizard, QWidget *parent = nullptr); /** * \brief Get magnitude limit set by user. * \return Magnitude limit set by user. */ double getMaglim() { return maglimSpinBox->value(); } /** * \brief Get FOV name set by user. * \return FOV name set by user. */ QString getFovName() { return fovCombo->currentText(); } /** * \brief Set object at which star hopper will begin. * \param obj Beginning object. */ void setBeginObject(SkyObject *obj); private slots: /** * \brief Slot: select beginning object from list. */ void slotSelectFromList(); /** * \brief Slot: point beginning object on SkyMap. */ void slotPointObject(); /** * \brief Slot: open details window. */ void slotDetails(); /** * \brief Slot: begin capture. */ void slotBeginCapture(); private: /** * \brief Setup widgets. */ void setupWidgets(); /** * \brief Setup signal-slot connections. */ void setupConnections(); PrintingWizard *m_ParentWizard; }; #endif // PWIZFOVSH_H diff --git a/kstars/printing/pwizfovtypeselection.h b/kstars/printing/pwizfovtypeselection.h index ca67cb400..4e189ccd2 100644 --- a/kstars/printing/pwizfovtypeselection.h +++ b/kstars/printing/pwizfovtypeselection.h @@ -1,48 +1,48 @@ /*************************************************************************** pwizfovtypeselection.h - K Desktop Planetarium ------------------- begin : Sun Aug 7 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #ifndef PWIZFOVTYPESELECTION_H #define PWIZFOVTYPESELECTION_H #include "ui_pwizfovtypeselection.h" #include "printingwizard.h" /** * \class PWizFovTypeSelectionUI * \brief User interface for "Select FOV capture method" step of the Printing Wizard. * \author Rafał Kułaga */ class PWizFovTypeSelectionUI : public QFrame, public Ui::PWizFovTypeSelection { Q_OBJECT public: /** * \brief Constructor. */ - explicit PWizFovTypeSelectionUI(PrintingWizard *wizard, QWidget *parent = 0); + explicit PWizFovTypeSelectionUI(PrintingWizard *wizard, QWidget *parent = nullptr); /** * \brief Get selected FOV export method. * \return Selected FOV export method. */ PrintingWizard::FOV_TYPE getFovExportType(); private: PrintingWizard *m_ParentWizard; }; #endif // PWIZFOVTYPESELECTION_H diff --git a/kstars/printing/pwizobjectselection.h b/kstars/printing/pwizobjectselection.h index 9a215a710..c4227a12a 100644 --- a/kstars/printing/pwizobjectselection.h +++ b/kstars/printing/pwizobjectselection.h @@ -1,72 +1,72 @@ /*************************************************************************** pwizobjectselection.h - K Desktop Planetarium ------------------- begin : Wed Aug 3 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #ifndef PWIZOBJECTSELECTION_H #define PWIZOBJECTSELECTION_H #include "ui_pwizobjectselection.h" class PrintingWizard; class SkyObject; /** * \class PWizObjectSelectionUI * \brief User interface for "Select observed object" step of the Printing Wizard. * \author Rafał Kułaga */ class PWizObjectSelectionUI : public QFrame, public Ui::PWizObjectSelection { Q_OBJECT public: /** * \brief Constructor. */ - explicit PWizObjectSelectionUI(PrintingWizard *wizard, QWidget *parent = 0); + explicit PWizObjectSelectionUI(PrintingWizard *wizard, QWidget *parent = nullptr); /** * \brief Update UI elements for newly selected SkyObject. * \param obj Selected SkyObject. */ void setSkyObject(SkyObject *obj); /** * \brief Static function: get QString with basic information about SkyObject. * \param obj Selected SkyObject. */ static QString objectInfoString(SkyObject *obj); private slots: /** * \brief Slot: open "Find Object" dialog to select SkyObject. */ void slotSelectFromList(); /** * \brief Slot: enter object pointing mode to select SkyObject. */ void slotPointObject(); /** * \brief Slot: show "Details" window for selected object. */ void slotShowDetails(); private: PrintingWizard *m_ParentWizard; }; #endif // PWIZOBJECTSELECTION_H diff --git a/kstars/printing/pwizprint.cpp b/kstars/printing/pwizprint.cpp index 174324394..3c1bb4411 100644 --- a/kstars/printing/pwizprint.cpp +++ b/kstars/printing/pwizprint.cpp @@ -1,140 +1,140 @@ /*************************************************************************** pwizprint.cpp - K Desktop Planetarium ------------------- begin : Wed Aug 3 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #include "pwizprint.h" #include "finderchart.h" #include "kstars.h" #include "printingwizard.h" #include #include #include #include #include #include #include #include PWizPrintUI::PWizPrintUI(PrintingWizard *wizard, QWidget *parent) : QFrame(parent), m_ParentWizard(wizard) { setupUi(this); connect(previewButton, SIGNAL(clicked()), this, SLOT(slotPreview())); connect(printButton, SIGNAL(clicked()), this, SLOT(slotPrint())); connect(exportButton, SIGNAL(clicked()), this, SLOT(slotExport())); } void PWizPrintUI::slotPreview() { QPointer previewDlg(new QPrintPreviewDialog(m_ParentWizard->getPrinter(), KStars::Instance())); connect(previewDlg, SIGNAL(paintRequested(QPrinter*)), SLOT(slotPrintPreview(QPrinter*))); previewDlg->exec(); delete previewDlg; } void PWizPrintUI::slotPrintPreview(QPrinter *printer) { printDocument(printer); } void PWizPrintUI::slotPrint() { QPointer dialog(new QPrintDialog(m_ParentWizard->getPrinter(), KStars::Instance())); if (dialog->exec() == QDialog::Accepted) { printDocument(m_ParentWizard->getPrinter()); } delete dialog; } void PWizPrintUI::printDocument(QPrinter *printer) { m_ParentWizard->getFinderChart()->print(printer); } void PWizPrintUI::slotExport() { QUrl url = QFileDialog::getSaveFileUrl(KStars::Instance(), i18n("Export"), QUrl(QDir::homePath()), "application/pdf application/postscript application/vnd.oasis.opendocument.text"); //User cancelled file selection dialog - abort image export if (url.isEmpty()) { return; } //Warn user if file exists! if (QFile::exists(url.toLocalFile())) { int r = KMessageBox::warningContinueCancel( parentWidget(), i18n("A file named \"%1\" already exists. Overwrite it?", url.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite()); if (r == KMessageBox::Cancel) return; } QString urlStr = url.url(); if (!urlStr.contains(QDir::separator())) { urlStr = QDir::homePath() + '/' + urlStr; } QTemporaryFile tmpfile; tmpfile.open(); QString fname; if (url.isValid()) { if (url.isLocalFile()) { fname = url.toLocalFile(); } else { fname = tmpfile.fileName(); } //Determine desired image format from filename extension QString ext = fname.mid(fname.lastIndexOf(".") + 1); if (ext == "pdf" || ext == "ps") { m_ParentWizard->getFinderChart()->writePsPdf(fname); } else if (ext == "odt") { m_ParentWizard->getFinderChart()->writeOdt(fname); } else { return; } if (tmpfile.fileName() == fname) { //attempt to upload image to remote location if (KIO::storedHttpPost(&tmpfile, url)->exec() == false) //if(!KIO::NetAccess::upload(tmpfile.fileName(), url, this)) { QString message = i18n("Could not upload file to remote location: %1", url.url()); - KMessageBox::sorry(0, message, i18n("Could not upload file")); + KMessageBox::sorry(nullptr, message, i18n("Could not upload file")); } } } } diff --git a/kstars/printing/simplefovexporter.cpp b/kstars/printing/simplefovexporter.cpp index 03acf6bf2..d78e66286 100644 --- a/kstars/printing/simplefovexporter.cpp +++ b/kstars/printing/simplefovexporter.cpp @@ -1,216 +1,216 @@ /*************************************************************************** simplefovexporter.cpp - K Desktop Planetarium ------------------- begin : Thu Jul 7 2011 copyright : (C) 2011 by Rafał Kułaga email : rl.kulaga@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. * * * ***************************************************************************/ #include "simplefovexporter.h" #include "kstars.h" #include "kstarsdata.h" #include "skymap.h" #include "skyqpainter.h" #include "fov.h" #include "skymapcomposite.h" #include "kstars/Options.h" SimpleFovExporter::SimpleFovExporter() : m_KSData(KStarsData::Instance()), m_Map(KStars::Instance()->map()), m_StopClock(false), m_OverrideFovShape(false), - m_DrawFovSymbol(false), m_PrevClockState(false), m_PrevSlewing(false), m_PrevPoint(0), m_PrevZoom(0) + m_DrawFovSymbol(false), m_PrevClockState(false), m_PrevSlewing(false), m_PrevPoint(nullptr), m_PrevZoom(0) { } void SimpleFovExporter::exportFov(SkyPoint *point, FOV *fov, QPaintDevice *pd) { saveState(true); pExportFov(point, fov, pd); restoreState(true); } void SimpleFovExporter::exportFov(FOV *fov, QPaintDevice *pd) { - pExportFov(0, fov, pd); + pExportFov(nullptr, fov, pd); } void SimpleFovExporter::exportFov(QPaintDevice *pd) { SkyQPainter painter(m_Map, pd); painter.begin(); painter.drawSkyBackground(); // translate painter coordinates - it's necessary to extract only the area of interest (FOV) int dx = (m_Map->width() - pd->width()) / 2; int dy = (m_Map->height() - pd->height()) / 2; painter.translate(-dx, -dy); m_KSData->skyComposite()->draw(&painter); m_Map->getSkyMapDrawAbstract()->drawOverlays(painter, false); } void SimpleFovExporter::exportFov(const QList &points, const QList &fovs, const QList &pds) { Q_ASSERT(points.size() == fovs.size() && fovs.size() == pds.size()); saveState(true); for (int i = 0; i < points.size(); i++) { exportFov(points.value(i), fovs.at(i), pds.value(i)); } restoreState(true); } void SimpleFovExporter::exportFov(const QList &points, FOV *fov, const QList &pds) { Q_ASSERT(points.size() == pds.size()); saveState(true); for (int i = 0; i < points.size(); i++) { exportFov(points.at(i), fov, pds.at(i)); } restoreState(true); } void SimpleFovExporter::pExportFov(SkyPoint *point, FOV *fov, QPaintDevice *pd) { if (point) { // center sky map on selected point m_Map->setClickedPoint(point); m_Map->slotCenter(); } // this is temporary 'solution' that will be changed during the implementation of printing // on large paper sizes (>A4), in which case it'll be desirable to export high-res FOV // representations if (pd->height() > m_Map->height() || pd->width() > m_Map->width()) { return; } // calculate zoom factor double zoom = 0; QRegion region; int regionX(0), regionY(0); double fovSizeX(0), fovSizeY(0); if (fov->sizeX() > fov->sizeY()) { zoom = calculateZoomLevel(pd->width(), fov->sizeX()); // calculate clipping region size fovSizeX = calculatePixelSize(fov->sizeX(), zoom); fovSizeY = calculatePixelSize(fov->sizeY(), zoom); regionX = 0; regionY = 0.5 * (pd->height() - fovSizeY); } else { zoom = calculateZoomLevel(pd->height(), fov->sizeY()); // calculate clipping region size fovSizeX = calculatePixelSize(fov->sizeX(), zoom); fovSizeY = calculatePixelSize(fov->sizeY(), zoom); regionX = 0.5 * (pd->width() - fovSizeX); regionY = 0; } if (fov->shape() == FOV::SQUARE) { region = QRegion(regionX, regionY, fovSizeX, fovSizeY, QRegion::Rectangle); } else { region = QRegion(regionX, regionY, fovSizeX, fovSizeY, QRegion::Ellipse); } m_Map->setZoomFactor(zoom); SkyQPainter painter(m_Map, pd); painter.begin(); painter.drawSkyBackground(); if (!m_OverrideFovShape) { painter.setClipRegion(region); } // translate painter coordinates - it's necessary to extract only the area of interest (FOV) int dx = (m_Map->width() - pd->width()) / 2; int dy = (m_Map->height() - pd->height()) / 2; painter.translate(-dx, -dy); m_KSData->skyComposite()->draw(&painter); m_Map->getSkyMapDrawAbstract()->drawOverlays(painter, false); // reset painter coordinate transform to paint FOV symbol in the center painter.resetTransform(); if (m_DrawFovSymbol) { fov->draw(painter, zoom); } } void SimpleFovExporter::saveState(bool savePos) { // stop simulation if it's not already stopped m_PrevClockState = m_KSData->clock()->isActive(); if (m_StopClock && m_PrevClockState) { m_KSData->clock()->stop(); } // disable useAnimatedSlewing option m_PrevSlewing = Options::useAnimatedSlewing(); if (m_PrevSlewing) { Options::setUseAnimatedSlewing(false); } // save current central point and zoom level - m_PrevPoint = savePos ? m_Map->focusPoint() : 0; + m_PrevPoint = savePos ? m_Map->focusPoint() : nullptr; m_PrevZoom = Options::zoomFactor(); } void SimpleFovExporter::restoreState(bool restorePos) { // restore previous useAnimatedSlewing option if (m_PrevSlewing) { Options::setUseAnimatedSlewing(true); } if (restorePos) { // restore previous central point m_Map->setClickedPoint(m_PrevPoint); m_Map->slotCenter(); } // restore previous zoom level m_Map->setZoomFactor(m_PrevZoom); // restore clock state (if it was stopped) if (m_StopClock && m_PrevClockState) { m_KSData->clock()->start(); } } diff --git a/kstars/projections/equirectangularprojector.h b/kstars/projections/equirectangularprojector.h index 68dfd96ad..efc423fc6 100644 --- a/kstars/projections/equirectangularprojector.h +++ b/kstars/projections/equirectangularprojector.h @@ -1,44 +1,44 @@ /* 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. */ #ifndef EQUIRECTANGULARPROJECTOR_H #define EQUIRECTANGULARPROJECTOR_H #include "projector.h" /** * @class EquirectangularProjector * * Implememntation of Equirectangular projection * */ class EquirectangularProjector : public Projector { public: explicit EquirectangularProjector(const ViewParams &p); Projection type() const override; double radius() const override; bool unusablePoint(const QPointF &p) const override; - Vector2f toScreenVec(const SkyPoint *o, bool oRefract = true, bool *onVisibleHemisphere = 0) const override; + Vector2f toScreenVec(const SkyPoint *o, bool oRefract = true, bool *onVisibleHemisphere = nullptr) const override; SkyPoint fromScreen(const QPointF &p, dms *LST, const dms *lat) const override; - QVector groundPoly(SkyPoint *labelpoint = 0, bool *drawLabel = 0) const override; + QVector groundPoly(SkyPoint *labelpoint = nullptr, bool *drawLabel = nullptr) const override; void updateClipPoly() override; }; #endif // EQUIRECTANGULARPROJECTOR_H diff --git a/kstars/projections/projector.h b/kstars/projections/projector.h index 87481df5f..72583a340 100644 --- a/kstars/projections/projector.h +++ b/kstars/projections/projector.h @@ -1,296 +1,296 @@ /* 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. */ #pragma once #ifdef KSTARS_LITE #include "skymaplite.h" #else #include "skymap.h" #endif #include "skyobjects/skypoint.h" #if __GNUC__ > 5 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wignored-attributes" #endif #if __GNUC__ > 6 #pragma GCC diagnostic ignored "-Wint-in-bool-context" #endif #include #if __GNUC__ > 5 #pragma GCC diagnostic pop #endif #include #include #include using namespace Eigen; class KStarsData; /** This is just a container that holds infromation needed to do projections. */ class ViewParams { public: float width, height; float zoomFactor; bool useRefraction; bool useAltAz; bool fillGround; /// groundPoly(SkyPoint *labelpoint = 0, bool *drawLabel = 0) const; + virtual QVector groundPoly(SkyPoint *labelpoint = nullptr, bool *drawLabel = nullptr) const; /** * @brief updateClipPoly calculate the clipping polygen given the current FOV. */ virtual void updateClipPoly(); /** * @return the clipping polygen covering the visible sky area. Anything outside this polygon is * clipped by QPainter. */ virtual QPolygonF clipPoly() const; protected: /** * Get the radius of this projection's sky circle. * @return the radius in radians */ virtual double radius() const { return 2 * M_PI; } /** * This function handles some of the projection-specific code. * @see toScreen() */ virtual double projectionK(double x) const { return x; } /** * This function handles some of the projection-specific code. * @see toScreen() */ virtual double projectionL(double x) const { return x; } /** * This function returns the cosine of the maximum field angle, i.e., the maximum angular * distance from the focus for which a point should be projected. Default is 0, i.e., * 90 degrees. */ virtual double cosMaxFieldAngle() const { return 0; } /** * Helper function for drawing ground. * @return the point with Alt = 0, az = @p az */ static SkyPoint pointAt(double az); KStarsData *m_data { nullptr }; ViewParams m_vp; double m_sinY0 { 0 }; double m_cosY0 { 0 }; double m_fov { 0 }; QPolygonF m_clipPolygon; private: //Used by CheckVisibility double m_xrange { 0 }; bool m_isPoleVisible { false }; }; diff --git a/kstars/skycomponents/asteroidscomponent.cpp b/kstars/skycomponents/asteroidscomponent.cpp index 72955e684..beaff9b5a 100644 --- a/kstars/skycomponents/asteroidscomponent.cpp +++ b/kstars/skycomponents/asteroidscomponent.cpp @@ -1,319 +1,319 @@ /*************************************************************************** asteroidscomponent.cpp - K Desktop Planetarium ------------------- begin : 2005/30/08 copyright : (C) 2005 by Thomas Kabelmann email : thomas.kabelmann@gmx.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. * * * ***************************************************************************/ #include #include #include #include #include #include #include "asteroidscomponent.h" #ifndef KSTARS_LITE #include "kstars.h" #endif #include "auxiliary/filedownloader.h" #include "projections/projector.h" #include "solarsystemcomposite.h" #include "skycomponent.h" #include "skylabeler.h" #ifndef KSTARS_LITE #include "skymap.h" #else #include "kstarslite.h" #endif #include "skypainter.h" #include "Options.h" #include "skyobjects/ksasteroid.h" #include "kstarsdata.h" #include "ksfilereader.h" #include "auxiliary/kspaths.h" #include "auxiliary/ksnotification.h" AsteroidsComponent::AsteroidsComponent(SolarSystemComposite *parent) : SolarSystemListComponent(parent) { loadData(); } AsteroidsComponent::~AsteroidsComponent() { } bool AsteroidsComponent::selected() { return Options::showAsteroids(); } /* *@short Initialize the asteroids list. *Reads in the asteroids data from the asteroids.dat file. * * The data file is a CSV file with the following columns : * @li 1 full name [string] * @li 2 Modified Julian Day of orbital elements [int] * @li 3 perihelion distance in AU [double] * @li 4 semi-major axis * @li 5 eccentricity of orbit [double] * @li 6 inclination angle of orbit in degrees [double] * @li 7 argument of perihelion in degrees [double] * @li 8 longitude of the ascending node in degrees [double] * @li 9 mean anomaly * @li 10 time of perihelion passage (YYYYMMDD.DDD) [double] * @li 11 orbit solution ID [string] * @li 12 absolute magnitude [float] * @li 13 slope parameter [float] * @li 14 Near-Earth Object (NEO) flag [bool] * @li 15 comet total magnitude parameter [float] (we should remove this column) * @li 16 comet nuclear magnitude parameter [float] (we should remove this column) * @li 17 object diameter (from equivalent sphere) [float] * @li 18 object bi/tri-axial ellipsoid dimensions [string] * @li 19 geometric albedo [float] * @li 20 rotation period [float] * @li 21 orbital period [float] * @li 22 earth minimum orbit intersection distance [double] * @li 23 orbit classification [string] */ void AsteroidsComponent::loadData() { QString name, full_name, orbit_id, orbit_class, dimensions; int mJD; double q, a, e, dble_i, dble_w, dble_N, dble_M, H, G, earth_moid; long double JD; float diameter, albedo, rot_period, period; bool neo; emitProgressText(i18n("Loading asteroids")); // Clear lists qDeleteAll(m_ObjectList); m_ObjectList.clear(); objectLists(SkyObject::ASTEROID).clear(); objectNames(SkyObject::ASTEROID).clear(); QList> sequence; sequence.append(qMakePair(QString("full name"), KSParser::D_QSTRING)); sequence.append(qMakePair(QString("epoch_mjd"), KSParser::D_INT)); sequence.append(qMakePair(QString("q"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("a"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("e"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("i"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("w"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("om"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("ma"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("tp_calc"), KSParser::D_SKIP)); sequence.append(qMakePair(QString("orbit_id"), KSParser::D_QSTRING)); sequence.append(qMakePair(QString("H"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("G"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("neo"), KSParser::D_QSTRING)); sequence.append(qMakePair(QString("tp_calc"), KSParser::D_SKIP)); sequence.append(qMakePair(QString("M2"), KSParser::D_SKIP)); sequence.append(qMakePair(QString("diameter"), KSParser::D_FLOAT)); sequence.append(qMakePair(QString("extent"), KSParser::D_QSTRING)); sequence.append(qMakePair(QString("albedo"), KSParser::D_FLOAT)); sequence.append(qMakePair(QString("rot_period"), KSParser::D_FLOAT)); sequence.append(qMakePair(QString("per_y"), KSParser::D_FLOAT)); sequence.append(qMakePair(QString("moid"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("class"), KSParser::D_QSTRING)); //QString file_name = KSPaths::locate( QStandardPaths::DataLocation, ); QString file_name = KSPaths::locate(QStandardPaths::GenericDataLocation, QString("asteroids.dat")); KSParser asteroid_parser(file_name, '#', sequence); QHash row_content; while (asteroid_parser.HasNextRow()) { row_content = asteroid_parser.ReadNextRow(); full_name = row_content["full name"].toString(); full_name = full_name.trimmed(); int catN = full_name.section(' ', 0, 0).toInt(); name = full_name.section(' ', 1, -1); //JM temporary hack to avoid Europa,Io, and Asterope duplication if (name == "Europa" || name == "Io" || name == "Asterope") name += i18n(" (Asteroid)"); mJD = row_content["epoch_mjd"].toInt(); q = row_content["q"].toDouble(); a = row_content["a"].toDouble(); e = row_content["e"].toDouble(); dble_i = row_content["i"].toDouble(); dble_w = row_content["w"].toDouble(); dble_N = row_content["om"].toDouble(); dble_M = row_content["ma"].toDouble(); orbit_id = row_content["orbit_id"].toString(); H = row_content["H"].toDouble(); G = row_content["G"].toDouble(); neo = row_content["neo"].toString() == "Y"; diameter = row_content["diameter"].toFloat(); dimensions = row_content["extent"].toString(); albedo = row_content["albedo"].toFloat(); rot_period = row_content["rot_period"].toFloat(); period = row_content["per_y"].toFloat(); earth_moid = row_content["moid"].toDouble(); orbit_class = row_content["class"].toString(); JD = static_cast(mJD) + 2400000.5; KSAsteroid *new_asteroid = nullptr; // JM: Hack since asteroid file (Generated by JPL) is missing important Pluto data // I emailed JPL and this hack will be removed once they update the data! if (name == "Pluto") { diameter = 2368; new_asteroid = new KSAsteroid(catN, name, "pluto", JD, a, e, dms(dble_i), dms(dble_w), dms(dble_N), dms(dble_M), H, G); } else new_asteroid = new KSAsteroid(catN, name, QString(), JD, a, e, dms(dble_i), dms(dble_w), dms(dble_N), dms(dble_M), H, G); new_asteroid->setPerihelion(q); new_asteroid->setOrbitID(orbit_id); new_asteroid->setNEO(neo); new_asteroid->setDiameter(diameter); new_asteroid->setDimensions(dimensions); new_asteroid->setAlbedo(albedo); new_asteroid->setRotationPeriod(rot_period); new_asteroid->setPeriod(period); new_asteroid->setEarthMOID(earth_moid); new_asteroid->setOrbitClass(orbit_class); new_asteroid->setPhysicalSize(diameter); //new_asteroid->setAngularSize(0.005); m_ObjectList.append(new_asteroid); // Add name to the list of object names objectNames(SkyObject::ASTEROID).append(name); objectLists(SkyObject::ASTEROID).append(QPair(name, new_asteroid)); } } void AsteroidsComponent::draw(SkyPainter *skyp) { Q_UNUSED(skyp) #ifndef KSTARS_LITE if (!selected()) return; bool hideLabels = !Options::showAsteroidNames() || (SkyMap::Instance()->isSlewing() && Options::hideLabels()); double lgmin = log10(MINZOOM); double lgmax = log10(MAXZOOM); double lgz = log10(Options::zoomFactor()); double labelMagLimit = 2.5 + Options::asteroidLabelDensity() / 5.0; labelMagLimit += (15.0 - labelMagLimit) * (lgz - lgmin) / (lgmax - lgmin); if (labelMagLimit > 10.0) labelMagLimit = 10.0; //printf("labelMagLim = %.1f\n", labelMagLimit ); skyp->setBrush(QBrush(QColor("gray"))); foreach (SkyObject *so, m_ObjectList) { // FIXME: God help us! KSAsteroid *ast = (KSAsteroid *)so; if (ast->mag() > Options::magLimitAsteroid() || std::isnan(ast->mag()) != 0) continue; bool drawn = false; if (ast->image().isNull() == false) drawn = skyp->drawPlanet(ast); else drawn = skyp->drawPointSource(ast, ast->mag()); if (drawn && !(hideLabels || ast->mag() >= labelMagLimit)) SkyLabeler::AddLabel(ast, SkyLabeler::ASTEROID_LABEL); } #endif } SkyObject *AsteroidsComponent::objectNearest(SkyPoint *p, double &maxrad) { - SkyObject *oBest = 0; + SkyObject *oBest = nullptr; if (!selected()) - return 0; + return nullptr; foreach (SkyObject *o, m_ObjectList) { if (o->mag() > Options::magLimitAsteroid()) continue; double r = o->angularDistanceTo(p).Degrees(); if (r < maxrad) { oBest = o; maxrad = r; } } return oBest; } void AsteroidsComponent::updateDataFile() { downloadJob = new FileDownloader(); downloadJob->setProgressDialogEnabled(true, i18n("Asteroid Update"), i18n("Downloading asteroids updates...")); downloadJob->registerDataVerification([&](const QByteArray &data) { return data.startsWith("full_name");}); QObject::connect(downloadJob, SIGNAL(downloaded()), this, SLOT(downloadReady())); QObject::connect(downloadJob, SIGNAL(error(QString)), this, SLOT(downloadError(QString))); QUrl url = QUrl("https://ssd.jpl.nasa.gov/sbdb_query.cgi"); QByteArray mag = QString::number(Options::magLimitAsteroidDownload()).toUtf8(); QByteArray post_data = KSUtils::getJPLQueryString("ast", "AcBdBiBhBgBjBlBkBmBqBbAiAjAgAkAlApAqArAsBsBtCh", QVector{ { "Ai", "<", mag } }); downloadJob->post(url, post_data); } void AsteroidsComponent::downloadReady() { // Comment the first line QByteArray data = downloadJob->downloadedData(); data.insert(0, '#'); // Write data to asteroids.dat QFile file(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "asteroids.dat"); file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text); file.write(data); file.close(); // Reload asteroids loadData(); #ifdef KSTARS_LITE KStarsLite::Instance()->data()->setFullTimeUpdate(); #else KStars::Instance()->data()->setFullTimeUpdate(); #endif downloadJob->deleteLater(); } void AsteroidsComponent::downloadError(const QString &errorString) { KSNotification::error(i18n("Error downloading asteroids data: %1", errorString)); qDebug() << i18n("Error downloading asteroids data: %1", errorString); downloadJob->deleteLater(); } diff --git a/kstars/skycomponents/catalogcomponent.cpp b/kstars/skycomponents/catalogcomponent.cpp index 60eaa9457..f12e0d179 100644 --- a/kstars/skycomponents/catalogcomponent.cpp +++ b/kstars/skycomponents/catalogcomponent.cpp @@ -1,241 +1,241 @@ /*************************************************************************** catalogcomponent.cpp - K Desktop Planetarium ------------------- begin : 2005/17/08 copyright : (C) 2005 by Thomas Kabelmann email : thomas.kabelmann@gmx.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. * * * ***************************************************************************/ #include "catalogcomponent.h" #include "catalogdata.h" #include "kstarsdata.h" #include "skypainter.h" #include "skyobjects/starobject.h" #include "skyobjects/deepskyobject.h" CatalogComponent::CatalogComponent(SkyComposite *parent, const QString &catname, bool showerrs, int index, bool callLoadData) : ListComponent(parent), m_catName(catname), m_Showerrs(showerrs), m_ccIndex(index) { if (callLoadData) loadData(); } CatalogComponent::~CatalogComponent() { // EH? WHY IS THIS EMPTY? -- AS // FIXME: Check this and implement it properly when you're not as // desperate to sleep as I am right now. -- AS /* auto removeFromContainer = []( auto &thingToRemove, auto &container ) { container.remove( container.indexOf( thingToRemove ) ); }; foreach ( SkyObject *obj, m_ObjectList ) { // FIXME: Should the name removal be done here!? What part of this giant edifice of code is supposed to take care of this? SkyMapComposite? -- AS // FIXME: Removing from any of these containers is VERY SLOW (QList will be O(N) to find and O(1) to remove, whereas QVector will be O(N) to find and O(N) to remove...) -- AS removeFromComtainer( objectNames( obj->type() ), obj->name() ); removeFromContainer( objectLists(obj->type()), QPair(obj->name(), obj) ); removeFromContainer( objectLists(obj->type()), QPair(obj->longname(), obj) ); delete obj; } */ } void CatalogComponent::_loadData(bool includeCatalogDesignation) { if (includeCatalogDesignation) emitProgressText(i18n("Loading custom catalog: %1", m_catName)); else emitProgressText(i18n("Loading internal catalog: %1", m_catName)); QList> names; KStarsData::Instance()->catalogdb()->GetAllObjects(m_catName, m_ObjectList, names, this, includeCatalogDesignation); for (int iter = 0; iter < names.size(); ++iter) { if (names.at(iter).first <= SkyObject::TYPE_UNKNOWN) { //FIXME JM 2016-06-02: inefficient and costly check // Need better way around this //if (!objectNames(names.at(iter).first).contains(names.at(iter).second)) // AS: FIXME -- after Artem Fedoskin introduced the // objectLists, which is definitely better, we should // really work towards doing away with this. // N.B. It might be better to use a std::multiset instead // of QVector>, because it allows for inexpensive // O(log N) lookups, but the filter and find will be very // quick because of the binary search tree. HOWEVER, it // will come at the cost of a lot of code complexity, // since std:: containers may not work well out of the box // with Qt's MVC system, so this would be desirable only // if N (number of named objects in KStars) becomes very // very large such that the filtering in Find Dialog takes // too long. -- AS objectNames(names.at(iter).first).append(names.at(iter).second); } } //FIXME - get rid of objectNames completely. For now only KStars Lite uses objectLists for (int iter = 0; iter < m_ObjectList.size(); ++iter) { SkyObject *obj = m_ObjectList[iter]; Q_ASSERT(obj); if (obj->type() <= SkyObject::TYPE_UNKNOWN) { QVector> &objects = objectLists(obj->type()); bool dupName = false; bool dupLongname = false; QString name = obj->name(); QString longname = obj->longname(); //FIXME - find a better way to check for duplicates // FIXME: AS: There is an argument for why we may not want // to remove duplicates -- when the object is removed, all // names seem to be removed, so if there are two objects // in KStars with the same name in two different catalogs // (e.g. Arp 148 from Arp catalog, and Arp 148 from some // miscellaneous catalog), then disabling one catalog // removes the name entirely from the list. for (int i = 0; i < objects.size(); ++i) { if (name == objects.at(i).first) dupName = true; if (longname == objects.at(i).first) dupLongname = true; } if (!dupName) { objectLists(obj->type()).append(QPair(name, obj)); } if (!longname.isEmpty() && !dupLongname && name != longname) { objectLists(obj->type()).append(QPair(longname, obj)); } } } // Remove Duplicates (see FIXME by AS above) for (auto &list : objectNames()) list.removeDuplicates(); CatalogData loaded_catalog_data; KStarsData::Instance()->catalogdb()->GetCatalogData(m_catName, loaded_catalog_data); m_catColor = loaded_catalog_data.color; m_catFluxFreq = loaded_catalog_data.fluxfreq; m_catFluxUnit = loaded_catalog_data.fluxunit; } void CatalogComponent::update(KSNumbers *) { if (selected()) { KStarsData *data = KStarsData::Instance(); foreach (SkyObject *obj, m_ObjectList) { DeepSkyObject *dso = dynamic_cast(obj); StarObject *so = dynamic_cast(obj); Q_ASSERT(dso || so); // We either have stars, or deep sky objects if (dso) { // Update the deep sky object if need be if (dso->updateID != data->updateID()) { dso->updateID = data->updateID(); if (dso->updateNumID != data->updateNumID()) { dso->updateCoords(data->updateNum()); } dso->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } } else { // Do exactly the same thing for stars if (so->updateID != data->updateID()) { so->updateID = data->updateID(); if (so->updateNumID != data->updateNumID()) { so->updateCoords(data->updateNum()); } so->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } } } this->updateID = data->updateID(); } } void CatalogComponent::draw(SkyPainter *skyp) { if (!selected()) return; skyp->setBrush(Qt::NoBrush); skyp->setPen(QColor(m_catColor)); // Check if the coordinates have been updated if (updateID != KStarsData::Instance()->updateID()) - update(0); + update(nullptr); //Draw Custom Catalog objects // FIXME: Improve using HTM! foreach (SkyObject *obj, m_ObjectList) { if (obj->type() == 0) { StarObject *starobj = static_cast(obj); // FIXME SKYPAINTER skyp->drawPointSource(starobj, starobj->mag(), starobj->spchar()); } else { // FIXME: this PA calc is totally different from the one that was // in DeepSkyComponent which is now in SkyPainter .... O_o // --hdevalence // PA for Deep-Sky objects is 90 + PA because major axis is // horizontal at PA=0 // double pa = 90. + map->findPA( dso, o.x(), o.y() ); // // ^ Not sure if above is still valid -- asimha 2016/08/16 DeepSkyObject *dso = static_cast(obj); skyp->drawDeepSkyObject(dso, true); } } } bool CatalogComponent::getVisibility() { return (Options::showCatalog().at(m_ccIndex) > 0) ? true : false; } bool CatalogComponent::selected() { // Do not draw / update custom catalogs if show deep-sky is turned off, even if they are chosen. if (Options::showCatalogNames().contains(m_catName) && Options::showDeepSky()) return true; return false; } diff --git a/kstars/skycomponents/cometscomponent.cpp b/kstars/skycomponents/cometscomponent.cpp index 0764a5e11..76d8993a5 100644 --- a/kstars/skycomponents/cometscomponent.cpp +++ b/kstars/skycomponents/cometscomponent.cpp @@ -1,270 +1,270 @@ /*************************************************************************** cometscomponent.cpp - K Desktop Planetarium ------------------- begin : 2005/24/09 copyright : (C) 2005 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 #include #include #include #include #include #include "cometscomponent.h" #include "solarsystemcomposite.h" #include "Options.h" #include "skyobjects/kscomet.h" #include "ksutils.h" #ifndef KSTARS_LITE #include "kstars.h" #endif #include "kstarsdata.h" #include "ksfilereader.h" #include "auxiliary/kspaths.h" #ifndef KSTARS_LITE #include "skymap.h" #else #include "kstarslite.h" #endif #include "skylabeler.h" #include "skypainter.h" #include "projections/projector.h" #include "auxiliary/filedownloader.h" #include "kspaths.h" #include "ksutils.h" CometsComponent::CometsComponent(SolarSystemComposite *parent) : SolarSystemListComponent(parent) { loadData(); } CometsComponent::~CometsComponent() { } bool CometsComponent::selected() { return Options::showComets(); } /* * @short Initialize the comets list. * Reads in the comets data from the comets.dat file. * * Populate the list of Comets from the data file. * The data file is a CSV file with the following columns : * @li 1 full name [string] * @li 2 modified julian day of orbital elements [int] * @li 3 perihelion distance in AU [double] * @li 4 eccentricity of orbit [double] * @li 5 inclination angle of orbit in degrees [double] * @li 6 argument of perihelion in degrees [double] * @li 7 longitude of the ascending node in degrees [double] * @li 8 time of perihelion passage (YYYYMMDD.DDD) [double] * @li 9 orbit solution ID [string] * @li 10 Near-Earth Object (NEO) flag [bool] * @li 11 comet total magnitude parameter [float] * @li 12 comet nuclear magnitude parameter [float] * @li 13 object diameter (from equivalent sphere) [float] * @li 14 object bi/tri-axial ellipsoid dimensions [string] * @li 15 geometric albedo [float] * @li 16 rotation period [float] * @li 17 orbital period [float] * @li 18 earth minimum orbit intersection distance [double] * @li 19 orbit classification [string] * @li 20 comet total magnitude slope parameter * @li 21 comet nuclear magnitude slope parameter * @note See KSComet constructor for more details. */ void CometsComponent::loadData() { QString name, orbit_id, orbit_class, dimensions; bool neo; int mJD; double q, e, dble_i, dble_w, dble_N, Tp, earth_moid; long double JD; float M1, M2, K1, K2, diameter, albedo, rot_period, period; emitProgressText(i18n("Loading comets")); qDeleteAll(m_ObjectList); m_ObjectList.clear(); objectNames(SkyObject::COMET).clear(); objectLists(SkyObject::COMET).clear(); QList> sequence; sequence.append(qMakePair(QString("full name"), KSParser::D_QSTRING)); sequence.append(qMakePair(QString("epoch_mjd"), KSParser::D_INT)); sequence.append(qMakePair(QString("q"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("e"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("i"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("w"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("om"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("tp_calc"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("orbit_id"), KSParser::D_QSTRING)); sequence.append(qMakePair(QString("neo"), KSParser::D_QSTRING)); sequence.append(qMakePair(QString("M1"), KSParser::D_FLOAT)); sequence.append(qMakePair(QString("M2"), KSParser::D_FLOAT)); sequence.append(qMakePair(QString("diameter"), KSParser::D_FLOAT)); sequence.append(qMakePair(QString("extent"), KSParser::D_QSTRING)); sequence.append(qMakePair(QString("albedo"), KSParser::D_FLOAT)); sequence.append(qMakePair(QString("rot_period"), KSParser::D_FLOAT)); sequence.append(qMakePair(QString("per_y"), KSParser::D_FLOAT)); sequence.append(qMakePair(QString("moid"), KSParser::D_DOUBLE)); sequence.append(qMakePair(QString("class"), KSParser::D_QSTRING)); sequence.append(qMakePair(QString("H"), KSParser::D_SKIP)); sequence.append(qMakePair(QString("G"), KSParser::D_SKIP)); QString file_name = KSPaths::locate(QStandardPaths::GenericDataLocation, QString("comets.dat")); KSParser cometParser(file_name, '#', sequence); QHash row_content; while (cometParser.HasNextRow()) { - KSComet *com = 0; + KSComet *com = nullptr; row_content = cometParser.ReadNextRow(); name = row_content["full name"].toString(); name = name.trimmed(); mJD = row_content["epoch_mjd"].toInt(); q = row_content["q"].toDouble(); e = row_content["e"].toDouble(); dble_i = row_content["i"].toDouble(); dble_w = row_content["w"].toDouble(); dble_N = row_content["om"].toDouble(); Tp = row_content["tp_calc"].toDouble(); orbit_id = row_content["orbit_id"].toString(); neo = row_content["neo"] == "Y"; if (row_content["M1"].toFloat() == 0.0) M1 = 101.0; else M1 = row_content["M1"].toFloat(); if (row_content["M2"].toFloat() == 0.0) M2 = 101.0; else M2 = row_content["M2"].toFloat(); diameter = row_content["diameter"].toFloat(); dimensions = row_content["extent"].toString(); albedo = row_content["albedo"].toFloat(); rot_period = row_content["rot_period"].toFloat(); period = row_content["per_y"].toFloat(); earth_moid = row_content["moid"].toDouble(); orbit_class = row_content["class"].toString(); K1 = row_content["H"].toFloat(); K2 = row_content["G"].toFloat(); JD = static_cast(mJD) + 2400000.5; com = new KSComet(name, QString(), JD, q, e, dms(dble_i), dms(dble_w), dms(dble_N), Tp, M1, M2, K1, K2); com->setOrbitID(orbit_id); com->setNEO(neo); com->setDiameter(diameter); com->setDimensions(dimensions); com->setAlbedo(albedo); com->setRotationPeriod(rot_period); com->setPeriod(period); com->setEarthMOID(earth_moid); com->setOrbitClass(orbit_class); com->setAngularSize(0.005); m_ObjectList.append(com); // Add *short* name to the list of object names objectNames(SkyObject::COMET).append(com->name()); objectLists(SkyObject::COMET).append(QPair(com->name(), com)); } } void CometsComponent::draw(SkyPainter *skyp) { Q_UNUSED(skyp) #ifndef KSTARS_LITE if (!selected() || Options::zoomFactor() < 10 * MINZOOM) return; bool hideLabels = !Options::showCometNames() || (SkyMap::Instance()->isSlewing() && Options::hideLabels()); double rsunLabelLimit = Options::maxRadCometName(); //FIXME: Should these be config'able? skyp->setPen(QPen(QColor("transparent"))); skyp->setBrush(QBrush(QColor("white"))); foreach (SkyObject *so, m_ObjectList) { KSComet *com = (KSComet *)so; double mag = com->mag(); if (std::isnan(mag) == 0) { bool drawn = skyp->drawComet(com); if (drawn && !(hideLabels || com->rsun() >= rsunLabelLimit)) SkyLabeler::AddLabel(com, SkyLabeler::COMET_LABEL); } } #endif } void CometsComponent::updateDataFile() { downloadJob = new FileDownloader(); downloadJob->setProgressDialogEnabled(true, i18n("Comets Update"), i18n("Downloading comets updates...")); downloadJob->registerDataVerification([&](const QByteArray &data) { return data.startsWith("full_name");}); connect(downloadJob, SIGNAL(downloaded()), this, SLOT(downloadReady())); connect(downloadJob, SIGNAL(error(QString)), this, SLOT(downloadError(QString))); QUrl url = QUrl("https://ssd.jpl.nasa.gov/sbdb_query.cgi"); QByteArray post_data = KSUtils::getJPLQueryString("com", "AcBdBiBgBjBlBkBqBbAgAkAlApAqArAsBsBtChAmAn", QVector{ { "Af", "!=", "D" } }); downloadJob->post(url, post_data); } void CometsComponent::downloadReady() { // Comment the first line QByteArray data = downloadJob->downloadedData(); data.insert(0, '#'); // Write data to asteroids.dat QFile file(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "comets.dat"); file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text); file.write(data); file.close(); // Reload asteroids loadData(); #ifdef KSTARS_LITE KStarsLite::Instance()->data()->setFullTimeUpdate(); #else KStars::Instance()->data()->setFullTimeUpdate(); #endif downloadJob->deleteLater(); } void CometsComponent::downloadError(const QString &errorString) { #ifndef KSTARS_LITE - KMessageBox::error(0, i18n("Error downloading asteroids data: %1", errorString)); + KMessageBox::error(nullptr, i18n("Error downloading asteroids data: %1", errorString)); #else qDebug() << i18n("Error downloading comets data: %1", errorString); #endif downloadJob->deleteLater(); } diff --git a/kstars/skycomponents/constellationboundarylines.cpp b/kstars/skycomponents/constellationboundarylines.cpp index 41a003827..214262615 100644 --- a/kstars/skycomponents/constellationboundarylines.cpp +++ b/kstars/skycomponents/constellationboundarylines.cpp @@ -1,291 +1,291 @@ /*************************************************************************** constellationboundarylines.cpp - K Desktop Planetarium ------------------- begin : 25 Oct. 2005 copyright : (C) 2005 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 "constellationboundarylines.h" #include "ksfilereader.h" #include "kstarsdata.h" #include "linelist.h" #include "Options.h" #include "polylist.h" #ifdef KSTARS_LITE #include "skymaplite.h" #else #include "skymap.h" #endif #include "skypainter.h" #include "htmesh/MeshIterator.h" #include "skycomponents/skymapcomposite.h" #include ConstellationBoundaryLines::ConstellationBoundaryLines(SkyComposite *parent) : NoPrecessIndex(parent, i18n("Constellation Boundaries")) { m_skyMesh = SkyMesh::Instance(); m_polyIndexCnt = 0; for (int i = 0; i < m_skyMesh->size(); i++) { m_polyIndex.append(std::shared_ptr(new PolyListList())); } KStarsData *data = KStarsData::Instance(); int verbose = 0; // -1 => create cbounds-$x.idx on stdout // 0 => normal const char *fname = "cbounds.dat"; int flag = 0; double ra, dec = 0, lastRa, lastDec; std::shared_ptr lineList; std::shared_ptr polyList; bool ok = false; intro(); // Open the .idx file and skip past the first line - KSFileReader idxReader, *idxFile = 0; + KSFileReader idxReader, *idxFile = nullptr; QString idxFname = QString("cbounds-%1.idx").arg(SkyMesh::Instance()->level()); if (idxReader.open(idxFname)) { idxReader.readLine(); idxFile = &idxReader; } // now open the file that contains the points KSFileReader fileReader; if (!fileReader.open(fname)) return; fileReader.setProgress(i18n("Loading Constellation Boundaries"), 13124, 10); lastRa = lastDec = -1000.0; while (fileReader.hasMoreLines()) { QString line = fileReader.readLine(); fileReader.showProgress(); if (line.at(0) == '#') continue; // ignore comments if (line.at(0) == ':') // :constellation line { if (lineList.get()) appendLine(lineList); lineList.reset(); if (polyList.get()) appendPoly(polyList, idxFile, verbose); QString cName = line.mid(1); polyList.reset(new PolyList(cName)); if (verbose == -1) printf(":\n"); lastRa = lastDec = -1000.0; continue; } // read in the data from the line ra = line.midRef(0, 12).toDouble(&ok); if (ok) dec = line.midRef(13, 12).toDouble(&ok); if (ok) flag = line.midRef(26, 1).toInt(&ok); if (!ok) { fprintf(stderr, "%s: conversion error on line: %d\n", fname, fileReader.lineNumber()); continue; } if (ra == lastRa && dec == lastDec) { fprintf(stderr, "%s: tossing dupe on line %4d: (%f, %f)\n", fname, fileReader.lineNumber(), ra, dec); continue; } // always add the point to the boundary (and toss dupes) // By the time we come here, we should have polyList. Else we aren't doing good Q_ASSERT(polyList); // Is this the right fix? polyList->append(QPointF(ra, dec)); if (ra < 0) polyList->setWrapRA(true); if (flag) { if (!lineList.get()) lineList.reset(new LineList()); std::shared_ptr point(new SkyPoint(ra, dec)); point->EquatorialToHorizontal(data->lst(), data->geo()->lat()); lineList->append(std::move(point)); lastRa = ra; lastDec = dec; } else { if (lineList.get()) appendLine(lineList); lineList.reset(); lastRa = lastDec = -1000.0; } } if (lineList.get()) appendLine(lineList); if (polyList.get()) appendPoly(polyList, idxFile, verbose); } ConstellationBoundaryLines::~ConstellationBoundaryLines() { } bool ConstellationBoundaryLines::selected() { #ifndef KSTARS_LITE return Options::showCBounds() && !(Options::hideOnSlew() && Options::hideCBounds() && SkyMap::IsSlewing()); #else return Options::showCBounds() && !(Options::hideOnSlew() && Options::hideCBounds() && SkyMapLite::IsSlewing()); #endif } void ConstellationBoundaryLines::preDraw(SkyPainter *skyp) { QColor color = KStarsData::Instance()->colorScheme()->colorNamed("CBoundColor"); skyp->setPen(QPen(QBrush(color), 1, Qt::SolidLine)); } void ConstellationBoundaryLines::appendPoly(std::shared_ptr &polyList, KSFileReader *file, int debug) { if (!file || debug == -1) return appendPoly(polyList, debug); while (file->hasMoreLines()) { QString line = file->readLine(); if (line.at(0) == ':') return; Trixel trixel = line.toInt(); m_polyIndex[trixel]->append(polyList); } } void ConstellationBoundaryLines::appendPoly(const std::shared_ptr &polyList, int debug) { if (debug >= 0 && debug < m_skyMesh->debug()) debug = m_skyMesh->debug(); const IndexHash &indexHash = m_skyMesh->indexPoly(polyList->poly()); IndexHash::const_iterator iter = indexHash.constBegin(); while (iter != indexHash.constEnd()) { Trixel trixel = iter.key(); iter++; if (debug == -1) printf("%d\n", trixel); m_polyIndex[trixel]->append(polyList); } if (debug > 9) printf("PolyList: %3d: %d\n", ++m_polyIndexCnt, indexHash.size()); } PolyList *ConstellationBoundaryLines::ContainingPoly(SkyPoint *p) { //printf("called ContainingPoly(p)\n"); // we save the pointers in a hash because most often there is only one // constellation and we can avoid doing the expensive boundary calculations // and just return it if we know it is unique. We can avoid this minor // complication entirely if we use index(p) instead of aperture(p, r) // because index(p) always returns a single trixel index. QHash polyHash; QHash::const_iterator iter; //printf("\n"); // the boundaries don't precess so we use index() not aperture() m_skyMesh->index(p, 1.0, IN_CONSTELL_BUF); MeshIterator region(m_skyMesh, IN_CONSTELL_BUF); while (region.hasNext()) { Trixel trixel = region.next(); //printf("Trixel: %4d %s\n", trixel, m_skyMesh->indexToName( trixel ) ); std::shared_ptr polyListList = m_polyIndex[trixel]; //printf(" size: %d\n", polyListList->size() ); for (int i = 0; i < polyListList->size(); i++) { polyHash.insert(polyListList->at(i).get(), true); } } iter = polyHash.constBegin(); // Don't bother with boundaries if there is only one if (polyHash.size() == 1) return iter.key(); QPointF point(p->ra().Hours(), p->dec().Degrees()); QPointF wrapPoint(p->ra().Hours() - 24.0, p->dec().Degrees()); bool wrapRA = p->ra().Hours() > 12.0; while (iter != polyHash.constEnd()) { PolyList *polyList = iter.key(); iter++; //qDebug() << QString("checking %1 boundary\n").arg( polyList->name() ); const QPolygonF *poly = polyList->poly(); if (wrapRA && polyList->wrapRA()) { if (poly->containsPoint(wrapPoint, Qt::OddEvenFill)) return polyList; } else { if (poly->containsPoint(point, Qt::OddEvenFill)) return polyList; } } - return 0; + return nullptr; } //------------------------------------------------------------------- // The routines for providing public access to the boundary index // start here. (Some of them may not be needed (or working)). //------------------------------------------------------------------- QString ConstellationBoundaryLines::constellationName(SkyPoint *p) { PolyList *polyList = ContainingPoly(p); if (polyList) { return (Options::useLocalConstellNames() ? i18nc("Constellation name (optional)", polyList->name().toUpper().toLocal8Bit().data()) : polyList->name()); } return i18n("Unknown"); } diff --git a/kstars/skycomponents/deepskycomponent.cpp b/kstars/skycomponents/deepskycomponent.cpp index 1ef259d80..a9abbc4a0 100644 --- a/kstars/skycomponents/deepskycomponent.cpp +++ b/kstars/skycomponents/deepskycomponent.cpp @@ -1,755 +1,755 @@ /*************************************************************************** deepskycomponent.cpp - K Desktop Planetarium ------------------- begin : 2005/15/08 copyright : (C) 2005 by Thomas Kabelmann email : thomas.kabelmann@gmx.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. * * * ***************************************************************************/ #include "deepskycomponent.h" #include "ksfilereader.h" #include "kspaths.h" #include "kstarsdata.h" #include "Options.h" #include "skylabeler.h" #ifndef KSTARS_LITE #include "skymap.h" #endif #include "skymesh.h" #include "skypainter.h" #include "htmesh/MeshIterator.h" #include "projections/projector.h" #include "skyobjects/deepskyobject.h" DeepSkyComponent::DeepSkyComponent(SkyComposite *parent) : SkyComponent(parent) { m_skyMesh = SkyMesh::Instance(); // Add labels for (int i = 0; i <= MAX_LINENUMBER_MAG; i++) m_labelList[i] = new LabelList; loadData(); } DeepSkyComponent::~DeepSkyComponent() { clearList(m_MessierList); clearList(m_NGCList); clearList(m_ICList); clearList(m_OtherList); qDeleteAll(m_DeepSkyIndex); m_DeepSkyIndex.clear(); qDeleteAll(m_MessierIndex); m_MessierIndex.clear(); qDeleteAll(m_NGCIndex); m_NGCIndex.clear(); qDeleteAll(m_ICIndex); m_ICIndex.clear(); qDeleteAll(m_OtherIndex); m_OtherIndex.clear(); for (int i = 0; i <= MAX_LINENUMBER_MAG; i++) delete m_labelList[i]; } bool DeepSkyComponent::selected() { return Options::showDeepSky(); } void DeepSkyComponent::update(KSNumbers *) { } void DeepSkyComponent::loadData() { KStarsData *data = KStarsData::Instance(); //Check whether we need to concatenate a split NGC/IC catalog //(i.e., if user has downloaded the Steinicke catalog) mergeSplitFiles(); QList> sequence; QList widths; sequence.append(qMakePair(QString("Flag"), KSParser::D_QSTRING)); widths.append(1); sequence.append(qMakePair(QString("ID"), KSParser::D_INT)); widths.append(4); sequence.append(qMakePair(QString("suffix"), KSParser::D_QSTRING)); widths.append(1); sequence.append(qMakePair(QString("RA_H"), KSParser::D_INT)); widths.append(2); sequence.append(qMakePair(QString("RA_M"), KSParser::D_INT)); widths.append(2); sequence.append(qMakePair(QString("RA_S"), KSParser::D_FLOAT)); widths.append(4); sequence.append(qMakePair(QString("D_Sign"), KSParser::D_QSTRING)); widths.append(2); sequence.append(qMakePair(QString("Dec_d"), KSParser::D_INT)); widths.append(2); sequence.append(qMakePair(QString("Dec_m"), KSParser::D_INT)); widths.append(2); sequence.append(qMakePair(QString("Dec_s"), KSParser::D_INT)); widths.append(2); sequence.append(qMakePair(QString("BMag"), KSParser::D_QSTRING)); widths.append(6); sequence.append(qMakePair(QString("type"), KSParser::D_INT)); widths.append(2); sequence.append(qMakePair(QString("a"), KSParser::D_FLOAT)); widths.append(6); sequence.append(qMakePair(QString("b"), KSParser::D_FLOAT)); widths.append(6); sequence.append(qMakePair(QString("pa"), KSParser::D_QSTRING)); widths.append(4); sequence.append(qMakePair(QString("PGC"), KSParser::D_INT)); widths.append(7); sequence.append(qMakePair(QString("other cat"), KSParser::D_QSTRING)); widths.append(4); sequence.append(qMakePair(QString("other1"), KSParser::D_QSTRING)); widths.append(6); sequence.append(qMakePair(QString("other2"), KSParser::D_QSTRING)); widths.append(6); sequence.append(qMakePair(QString("Messr"), KSParser::D_QSTRING)); widths.append(2); sequence.append(qMakePair(QString("MessrNum"), KSParser::D_INT)); widths.append(4); sequence.append(qMakePair(QString("Longname"), KSParser::D_QSTRING)); //No width to be appended for last sequence object QString file_name = KSPaths::locate(QStandardPaths::GenericDataLocation, QString("ngcic.dat")); KSParser deep_sky_parser(file_name, '#', sequence, widths); deep_sky_parser.SetProgress(i18n("Loading NGC/IC objects"), 13444, 10); qDebug() << "Loading NGC/IC objects"; QHash row_content; while (deep_sky_parser.HasNextRow()) { row_content = deep_sky_parser.ReadNextRow(); QString iflag; QString cat; iflag = row_content["Flag"].toString().mid(0, 1); //check for NGC/IC catalog flag /* Q_ASSERT(iflag == "I" || iflag == "N" || iflag == " "); // (spacetime): ^ Why an assert? Change in implementation of ksparser // might result in crash for no reason. // n.b. We also allow non-NGC/IC objects which have a blank iflag */ float mag(1000.0); int type, ingc, imess(-1), pa; int pgc, ugc; QString ss, name, name2, longname; QString cat2; // Designation if (iflag == "I") cat = "IC"; else if (iflag == "N") cat = "NGC"; ingc = row_content["ID"].toInt(); // NGC/IC catalog number if (ingc == 0) cat.clear(); //object is not in NGC or IC catalogs QString suffix = row_content["suffix"].toString(); // multipliticity suffixes, eg: the 'A' in NGC 4945A Q_ASSERT(suffix.isEmpty() || (suffix.at(0) >= QChar('A') && suffix.at(0) <= QChar('Z')) || (suffix.at(0) >= QChar('a') && suffix.at(0) <= QChar('z'))); //coordinates int rah = row_content["RA_H"].toInt(); int ram = row_content["RA_M"].toInt(); float ras = row_content["RA_S"].toFloat(); QString sgn = row_content["D_Sign"].toString(); int dd = row_content["Dec_d"].toInt(); int dm = row_content["Dec_m"].toInt(); int ds = row_content["Dec_s"].toInt(); if (!((0.0 <= rah && rah < 24.0) || (0.0 <= ram && ram < 60.0) || (0.0 <= ras && ras < 60.0) || (0.0 <= dd && dd <= 90.0) || (0.0 <= dm && dm < 60.0) || (0.0 <= ds && ds < 60.0))) { qDebug() << "Bad coordinates while processing NGC/IC object: " << cat << ingc; qDebug() << "RA H:M:S = " << rah << ":" << ram << ":" << ras << "; Dec D:M:S = " << dd << ":" << dm << ":" << ds; Q_ASSERT(false); } //Ignore lines with no coordinate values if not debugging if (rah == 0 && ram == 0 && ras == 0) continue; //B magnitude ss = row_content["BMag"].toString(); if (ss.isEmpty()) { mag = 99.9f; } else { mag = ss.toFloat(); } //object type type = row_content["type"].toInt(); //major and minor axes float a = row_content["a"].toFloat(); float b = row_content["b"].toFloat(); //position angle. The catalog PA is zero when the Major axis //is horizontal. But we want the angle measured from North, so //we set PA = 90 - pa. ss = row_content["pa"].toString(); if (ss.isEmpty()) { pa = 90; } else { pa = 90 - ss.toInt(); } //PGC number pgc = row_content["PGC"].toInt(); //UGC number if (row_content["other cat"].toString().trimmed() == "UGC") { ugc = row_content["other1"].toString().toInt(); } else { ugc = 0; } //Messier number if (row_content["Messr"].toString().trimmed() == "M") { cat2 = cat; if (ingc == 0) cat2.clear(); cat = 'M'; imess = row_content["MessrNum"].toInt(); } longname = row_content["Longname"].toString(); dms r; r.setH(rah, ram, int(ras)); dms d(dd, dm, ds); if (sgn == "-") { d.setD(-1.0 * d.Degrees()); } bool hasName = true; QString snum; if (cat == "IC" || cat == "NGC") { snum.setNum(ingc); name = cat + ' ' + ((suffix.isEmpty()) ? snum : (snum + suffix)); } else if (cat == "M") { snum.setNum(imess); name = cat + ' ' + snum; // Note: Messier has no suffixes if (cat2 == "NGC" || cat2 == "IC") { snum.setNum(ingc); name2 = cat2 + ' ' + ((suffix.isEmpty()) ? snum : (snum + suffix)); } else { name2.clear(); } } else { if (!longname.isEmpty()) name = longname; else { hasName = false; name = i18n("Unnamed Object"); } } name = i18nc("object name (optional)", name.toLatin1().constData()); if (!longname.isEmpty()) longname = i18nc("object name (optional)", longname.toLatin1().constData()); // create new deepskyobject - DeepSkyObject *o = 0; + DeepSkyObject *o = nullptr; if (type == 0) type = 1; //Make sure we use CATALOG_STAR, not STAR o = new DeepSkyObject(type, r, d, mag, name, name2, longname, cat, a, b, pa, pgc, ugc); o->EquatorialToHorizontal(data->lst(), data->geo()->lat()); // Add the name(s) to the nameHash for fast lookup -jbb if (hasName) { nameHash[name.toLower()] = o; if (!longname.isEmpty()) nameHash[longname.toLower()] = o; if (!name2.isEmpty()) nameHash[name2.toLower()] = o; } Trixel trixel = m_skyMesh->index(o); //Assign object to general DeepSkyObjects list, //and a secondary list based on its catalog. m_DeepSkyList.append(o); appendIndex(o, &m_DeepSkyIndex, trixel); if (o->isCatalogM()) { m_MessierList.append(o); appendIndex(o, &m_MessierIndex, trixel); } else if (o->isCatalogNGC()) { m_NGCList.append(o); appendIndex(o, &m_NGCIndex, trixel); } else if (o->isCatalogIC()) { m_ICList.append(o); appendIndex(o, &m_ICIndex, trixel); } else { m_OtherList.append(o); appendIndex(o, &m_OtherIndex, trixel); } // JM: VERY INEFFICIENT. Disabling for now until we figure out how to deal with dups. QSet? //if ( ! name.isEmpty() && !objectNames(type).contains(name)) if (!name.isEmpty()) { objectNames(type).append(name); objectLists(type).append(QPair(name, o)); } //Add long name to the list of object names //if ( ! longname.isEmpty() && longname != name && !objectNames(type).contains(longname)) if (!longname.isEmpty() && longname != name) { objectNames(type).append(longname); objectLists(type).append(QPair(longname, o)); } deep_sky_parser.ShowProgress(); } for (auto &list : objectNames()) list.removeDuplicates(); } void DeepSkyComponent::mergeSplitFiles() { //If user has downloaded the Steinicke NGC/IC catalog, then it is //split into multiple files. Concatenate these into a single file. QString firstFile = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "ngcic01.dat"; if (!QFile::exists(firstFile)) return; QDir localDir = QFileInfo(firstFile).absoluteDir(); QStringList catFiles = localDir.entryList(QStringList("ngcic??.dat")); qDebug() << "Merging split NGC/IC files" << endl; QString buffer; for (auto &fname : catFiles) { QFile f(localDir.absoluteFilePath(fname)); if (f.open(QIODevice::ReadOnly)) { QTextStream stream(&f); buffer += stream.readAll(); f.close(); } else { qDebug() << QString("Error: Could not open %1 for reading").arg(fname) << endl; } } QFile fout(localDir.absoluteFilePath("ngcic.dat")); if (fout.open(QIODevice::WriteOnly)) { QTextStream oStream(&fout); oStream << buffer; fout.close(); //Remove the split-files foreach (const QString &fname, catFiles) { QString fullname = localDir.absoluteFilePath(fname); //DEBUG qDebug() << "Removing " << fullname << " ..." << endl; QFile::remove(fullname); } } } void DeepSkyComponent::appendIndex(DeepSkyObject *o, DeepSkyIndex *dsIndex, Trixel trixel) { if (!dsIndex->contains(trixel)) { dsIndex->insert(trixel, new DeepSkyList()); } dsIndex->value(trixel)->append(o); } void DeepSkyComponent::draw(SkyPainter *skyp) { #ifndef KSTARS_LITE if (!selected()) return; bool drawFlag; drawFlag = Options::showMessier() && !(Options::hideOnSlew() && Options::hideMessier() && SkyMap::IsSlewing()); drawDeepSkyCatalog(skyp, drawFlag, &m_MessierIndex, "MessColor", Options::showMessierImages() && !Options::showHIPS()); drawFlag = Options::showNGC() && !(Options::hideOnSlew() && Options::hideNGC() && SkyMap::IsSlewing()); drawDeepSkyCatalog(skyp, drawFlag, &m_NGCIndex, "NGCColor"); drawFlag = Options::showIC() && !(Options::hideOnSlew() && Options::hideIC() && SkyMap::IsSlewing()); drawDeepSkyCatalog(skyp, drawFlag, &m_ICIndex, "ICColor"); drawFlag = Options::showOther() && !(Options::hideOnSlew() && Options::hideOther() && SkyMap::IsSlewing()); drawDeepSkyCatalog(skyp, drawFlag, &m_OtherIndex, "NGCColor"); #else Q_UNUSED(skyp) #endif } void DeepSkyComponent::drawDeepSkyCatalog(SkyPainter *skyp, bool drawObject, DeepSkyIndex *dsIndex, const QString &colorString, bool drawImage) { #ifndef KSTARS_LITE if (!(drawObject || drawImage)) return; SkyMap *map = SkyMap::Instance(); const Projector *proj = map->projector(); KStarsData *data = KStarsData::Instance(); UpdateID updateID = data->updateID(); UpdateID updateNumID = data->updateNumID(); skyp->setPen(data->colorScheme()->colorNamed(colorString)); skyp->setBrush(Qt::NoBrush); m_hideLabels = (map->isSlewing() && Options::hideOnSlew()) || !(Options::showDeepSkyMagnitudes() || Options::showDeepSkyNames()); double maglim = Options::magLimitDrawDeepSky(); bool showUnknownMagObjects = Options::showUnknownMagObjects(); //adjust maglimit for ZoomLevel double lgmin = log10(MINZOOM); double lgmax = log10(MAXZOOM); double lgz = log10(Options::zoomFactor()); if (lgz <= 0.75 * lgmax) maglim -= (Options::magLimitDrawDeepSky() - Options::magLimitDrawDeepSkyZoomOut()) * (0.75 * lgmax - lgz) / (0.75 * lgmax - lgmin); m_zoomMagLimit = maglim; double labelMagLim = Options::deepSkyLabelDensity(); labelMagLim += (Options::magLimitDrawDeepSky() - labelMagLim) * (lgz - lgmin) / (lgmax - lgmin); if (labelMagLim > Options::magLimitDrawDeepSky()) labelMagLim = Options::magLimitDrawDeepSky(); //DrawID drawID = m_skyMesh->drawID(); MeshIterator region(m_skyMesh, DRAW_BUF); while (region.hasNext()) { Trixel trixel = region.next(); DeepSkyList *dsList = dsIndex->value(trixel); - if (dsList == 0) + if (dsList == nullptr) continue; for (int j = 0; j < dsList->size(); j++) { DeepSkyObject *obj = dsList->at(j); //if ( obj->drawID == drawID ) continue; // only draw each line once //obj->drawID = drawID; if (obj->updateID != updateID) { obj->updateID = updateID; if (obj->updateNumID != updateNumID) { obj->updateCoords(data->updateNum()); } obj->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } float mag = obj->mag(); float size = obj->a() * dms::PI * Options::zoomFactor() / 10800.0; //only draw objects if flags set, it's bigger than 1 pixel (unless //zoom > 2000.), and it's brighter than maglim (unless mag is //undefined (=99.9) bool sizeCriterion = (size > 1.0 || Options::zoomFactor() > 2000.); bool magCriterion = (mag < (float)maglim) || (showUnknownMagObjects && (std::isnan(mag) || mag > 36.0)); if (sizeCriterion && magCriterion) { bool drawn = skyp->drawDeepSkyObject(obj, drawImage); if (drawn && !(m_hideLabels || mag > labelMagLim)) addLabel(proj->toScreen(obj), obj); //FIXME: find a better way to do above } } } #else Q_UNUSED(skyp) Q_UNUSED(drawObject) Q_UNUSED(dsIndex) Q_UNUSED(colorString) Q_UNUSED(drawImage) #endif } void DeepSkyComponent::addLabel(const QPointF &p, DeepSkyObject *obj) { int idx = int(obj->mag() * 10.0); if (idx < 0) idx = 0; if (idx > MAX_LINENUMBER_MAG) idx = MAX_LINENUMBER_MAG; m_labelList[idx]->append(SkyLabel(p, obj)); } void DeepSkyComponent::drawLabels() { #ifndef KSTARS_LITE if (m_hideLabels) return; SkyLabeler *labeler = SkyLabeler::Instance(); labeler->setPen(QColor(KStarsData::Instance()->colorScheme()->colorNamed("DSNameColor"))); int max = int(m_zoomMagLimit * 10.0); if (max < 0) max = 0; if (max > MAX_LINENUMBER_MAG) max = MAX_LINENUMBER_MAG; for (int i = 0; i <= max; i++) { LabelList *list = m_labelList[i]; for (int j = 0; j < list->size(); j++) { labeler->drawNameLabel(list->at(j).obj, list->at(j).o); } list->clear(); } #endif } SkyObject *DeepSkyComponent::findByName(const QString &name) { return nameHash[name.toLower()]; } void DeepSkyComponent::objectsInArea(QList &list, const SkyRegion ®ion) { for (SkyRegion::const_iterator it = region.constBegin(); it != region.constEnd(); ++it) { Trixel trixel = it.key(); if (m_DeepSkyIndex.contains(trixel)) { DeepSkyList *dsoList = m_DeepSkyIndex.value(trixel); for (DeepSkyList::iterator dsit = dsoList->begin(); dsit != dsoList->end(); ++dsit) list.append(*dsit); } } } //we multiply each catalog's smallest angular distance by the //following factors before selecting the final nearest object: // IC catalog = 0.8 // NGC catalog = 0.5 // "other" catalog = 0.4 // Messier object = 0.25 SkyObject *DeepSkyComponent::objectNearest(SkyPoint *p, double &maxrad) { if (!selected()) - return 0; + return nullptr; - SkyObject *oTry = 0; - SkyObject *oBest = 0; + SkyObject *oTry = nullptr; + SkyObject *oBest = nullptr; double rTry = maxrad; double rBest = maxrad; double r; DeepSkyList *dsList; SkyObject *obj; MeshIterator region(m_skyMesh, OBJ_NEAREST_BUF); while (region.hasNext()) { dsList = m_ICIndex[region.next()]; if (!dsList) continue; for (int i = 0; i < dsList->size(); ++i) { obj = dsList->at(i); r = obj->angularDistanceTo(p).Degrees(); if (r < rTry) { rTry = r; oTry = obj; } } } rTry *= 0.8; if (rTry < rBest) { rBest = rTry; oBest = oTry; } rTry = maxrad; region.reset(); while (region.hasNext()) { dsList = m_NGCIndex[region.next()]; if (!dsList) continue; for (int i = 0; i < dsList->size(); ++i) { obj = dsList->at(i); r = obj->angularDistanceTo(p).Degrees(); if (r < rTry) { rTry = r; oTry = obj; } } } rTry *= 0.6; if (rTry < rBest) { rBest = rTry; oBest = oTry; } rTry = maxrad; region.reset(); while (region.hasNext()) { dsList = m_OtherIndex[region.next()]; if (!dsList) continue; for (int i = 0; i < dsList->size(); ++i) { obj = dsList->at(i); r = obj->angularDistanceTo(p).Degrees(); if (r < rTry) { rTry = r; oTry = obj; } } } rTry *= 0.6; if (rTry < rBest) { rBest = rTry; oBest = oTry; } rTry = maxrad; region.reset(); while (region.hasNext()) { dsList = m_MessierIndex[region.next()]; if (!dsList) continue; for (int i = 0; i < dsList->size(); ++i) { obj = dsList->at(i); r = obj->angularDistanceTo(p).Degrees(); if (r < rTry) { rTry = r; oTry = obj; } } } // -jbb: this is the template of the non-indexed way // //foreach ( SkyObject *o, m_MessierList ) { // r = o->angularDistanceTo( p ).Degrees(); // if ( r < rTry ) { // rTry = r; // oTry = o; // } //} rTry *= 0.5; if (rTry < rBest) { rBest = rTry; oBest = oTry; } maxrad = rBest; return oBest; } void DeepSkyComponent::clearList(QList &list) { while (!list.isEmpty()) { SkyObject *o = list.takeFirst(); removeFromNames(o); delete o; } } diff --git a/kstars/skycomponents/deepskycomponent.h b/kstars/skycomponents/deepskycomponent.h index 2d51a3e68..bb542d325 100644 --- a/kstars/skycomponents/deepskycomponent.h +++ b/kstars/skycomponents/deepskycomponent.h @@ -1,169 +1,169 @@ /*************************************************************************** deepskycomponent.h - K Desktop Planetarium ------------------- begin : 2005/15/08 copyright : (C) 2005 by Thomas Kabelmann email : thomas.kabelmann@gmx.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 "skycomponent.h" #include "skylabel.h" class QPointF; #ifdef KSTARS_LITE class DeepSkyItem; #endif class DeepSkyObject; class KSNumbers; class SkyMap; class SkyMesh; class SkyPoint; // NOTE: Although the following symbol has nothing to do with line // number in any file, we use this name to keep consistency in naming // conventions with StarComponent #define MAX_LINENUMBER_MAG 90 typedef QVector DeepSkyList; typedef QHash DeepSkyIndex; /** * @class DeepSkyComponent * Represents the deep sky objects separated by catalogs. * Custom Catalogs are a standalone component. * @note this Component is similar to ListComponent, but * the deep sky objects are stored in four separate QLists. * @author Thomas Kabelmann * @version 0.1 */ class DeepSkyComponent : public SkyComponent { #ifdef KSTARS_LITE friend class DeepSkyItem; #endif public: explicit DeepSkyComponent(SkyComposite *); ~DeepSkyComponent() override; void draw(SkyPainter *skyp) override; /** * @short draw all the labels in the prioritized LabelLists and then * clear the LabelLists. */ void drawLabels(); /** * @short Update the sky positions of this component. FIXME -jbb does nothing now * * This function usually just updates the Horizontal (Azimuth/Altitude) coordinates of the * objects in this component. If the KSNumbers argument is not nullptr, this function also * recomputes precession and nutation for the date in KSNumbers. * @p num Pointer to the KSNumbers object * @note By default, the num parameter is nullptr, indicating that Precession/Nutation * computation should be skipped; this computation is only occasionally required. */ - void update(KSNumbers *num = 0) override; + void update(KSNumbers *num = nullptr) override; /** * @short Search the children of this SkyComponent for a SkyObject whose name matches the argument * @p name the name to be matched * @return a pointer to the SkyObject whose name matches the argument, or a nullptr pointer * if no match was found. */ SkyObject *findByName(const QString &name) override; /** * @short Searches the region(s) and appends the SkyObjects found to the list of sky objects * * Look for a SkyObject that is in one of the regions * If found, then append to the list of sky objects * @p list list of SkyObject to which matching list has to be appended to * @p region defines the regions in which the search for SkyObject should be done within * @return void */ void objectsInArea(QList &list, const SkyRegion ®ion) override; SkyObject *objectNearest(SkyPoint *p, double &maxrad) override; const QList &objectList() const { return m_DeepSkyList; } bool selected() override; private: /** * @short Read the ngcic.dat deep-sky database. * Parse all lines from the deep-sky object catalog files. Construct a DeepSkyObject * from the data in each line, and add it to the DeepSkyComponent. * * Each line in the file is parsed according to column position: * @li 0 IC indicator [char] If 'I' then IC object; if ' ' then NGC object * @li 1-4 Catalog number [int] The NGC/IC catalog ID number * @li 6-8 Constellation code (IAU abbreviation) * @li 10-11 RA hours [int] * @li 13-14 RA minutes [int] * @li 16-19 RA seconds [float] * @li 21 Dec sign [char; '+' or '-'] * @li 22-23 Dec degrees [int] * @li 25-26 Dec minutes [int] * @li 28-29 Dec seconds [int] * @li 31 Type ID [int] Indicates object type; see TypeName array in kstars.cpp * @li 33-36 Type details [string] (not yet used) * @li 38-41 Magnitude [float] can be blank * @li 43-48 Major axis length, in arcmin [float] can be blank * @li 50-54 Minor axis length, in arcmin [float] can be blank * @li 56-58 Position angle, in degrees [int] can be blank * @li 60-62 Messier catalog number [int] can be blank * @li 64-69 PGC Catalog number [int] can be blank * @li 71-75 UGC Catalog number [int] can be blank * @li 77-END Common name [string] can be blank * @return true if data file is successfully read. */ void loadData(); void clearList(QList &list); void mergeSplitFiles(); void drawDeepSkyCatalog(SkyPainter *skyp, bool drawObject, DeepSkyIndex *dsIndex, const QString &colorString, bool drawImage = false); QList m_DeepSkyList; QList m_MessierList; QList m_NGCList; QList m_ICList; QList m_OtherList; LabelList *m_labelList[MAX_LINENUMBER_MAG + 1]; bool m_hideLabels { false }; double m_zoomMagLimit { 0 }; SkyMesh *m_skyMesh { nullptr }; DeepSkyIndex m_DeepSkyIndex; DeepSkyIndex m_MessierIndex; DeepSkyIndex m_NGCIndex; DeepSkyIndex m_ICIndex; DeepSkyIndex m_OtherIndex; void appendIndex(DeepSkyObject *o, DeepSkyIndex *dsIndex, Trixel trixel); QHash nameHash; /** @short adds a label to the lists of labels to be drawn prioritized by magnitude. */ void addLabel(const QPointF &p, DeepSkyObject *obj); }; diff --git a/kstars/skycomponents/deepstarcomponent.cpp b/kstars/skycomponents/deepstarcomponent.cpp index 0e45980c7..172f660e4 100644 --- a/kstars/skycomponents/deepstarcomponent.cpp +++ b/kstars/skycomponents/deepstarcomponent.cpp @@ -1,659 +1,659 @@ /*************************************************************************** deepstarcomponent.cpp - K Desktop Planetarium ------------------- begin : Fri 1st Aug 2008 copyright : (C) 2008 Akarsh Simha, Thomas Kabelmann email : akarshsimha@gmail.com, thomas.kabelmann@gmx.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. * * * ***************************************************************************/ #include "deepstarcomponent.h" #include "byteorder.h" #include "kstarsdata.h" #include "Options.h" #ifndef KSTARS_LITE #include "skymap.h" #endif #include "skymesh.h" #include "skypainter.h" #include "starblock.h" #include "starcomponent.h" #include "htmesh/MeshIterator.h" #include "projections/projector.h" #include #include #include #ifdef _WIN32 #include #endif DeepStarComponent::DeepStarComponent(SkyComposite *parent, QString fileName, float trigMag, bool staticstars) : ListComponent(parent), m_reindexNum(J2000), triggerMag(trigMag), m_FaintMagnitude(-5.0), staticStars(staticstars), dataFileName(fileName) { fileOpened = false; openDataFile(); if (staticStars) loadStaticStars(); qCInfo(KSTARS) << "Loaded DSO catalog file: " << dataFileName; } DeepStarComponent::~DeepStarComponent() { if (fileOpened) starReader.closeFile(); fileOpened = false; } bool DeepStarComponent::loadStaticStars() { FILE *dataFile; if (!staticStars) return true; if (!fileOpened) return false; dataFile = starReader.getFileHandle(); rewind(dataFile); if (!starReader.readHeader()) { qCCritical(KSTARS) << "Error reading header of catalog file " << dataFileName << ": " << starReader.getErrorNumber() << ": " << starReader.getError() << endl; return false; } quint8 recordSize = starReader.guessRecordSize(); if (recordSize != 16 && recordSize != 32) { qCCritical(KSTARS) << "Cannot understand catalog file " << dataFileName << endl; return false; } //KDE_fseek(dataFile, starReader.getDataOffset(), SEEK_SET); QT_FSEEK(dataFile, starReader.getDataOffset(), SEEK_SET); qint16 faintmag; quint8 htm_level; quint16 t_MSpT; int ret = 0; ret = fread(&faintmag, 2, 1, dataFile); if (starReader.getByteSwap()) faintmag = bswap_16(faintmag); ret = fread(&htm_level, 1, 1, dataFile); ret = fread(&t_MSpT, 2, 1, dataFile); // Unused if (starReader.getByteSwap()) faintmag = bswap_16(faintmag); // TODO: Read the multiplying factor from the dataFile m_FaintMagnitude = faintmag / 100.0; if (htm_level != m_skyMesh->level()) qCWarning(KSTARS) << "HTM Level in shallow star data file and HTM Level in m_skyMesh do not match. EXPECT TROUBLE!"; // JM 2012-12-05: Breaking into 2 loops instead of one previously with multiple IF checks for recordSize // While the CPU branch prediction might not suffer any penalities since the branch prediction after a few times // should always gets it right. It's better to do it this way to avoid any chances since the compiler might not optimize it. if (recordSize == 32) { for (Trixel i = 0; i < m_skyMesh->size(); ++i) { Trixel trixel = i; quint64 records = starReader.getRecordCount(i); std::shared_ptr SB(new StarBlock(records)); if (!SB.get()) qCCritical(KSTARS) << "ERROR: Could not allocate new StarBlock to hold shallow unnamed stars for trixel " << trixel; m_starBlockList.at(trixel)->setStaticBlock(SB); for (quint64 j = 0; j < records; ++j) { bool fread_success = fread(&stardata, sizeof(StarData), 1, dataFile); if (!fread_success) { qCCritical(KSTARS) << "ERROR: Could not read StarData structure for star #" << j << " under trixel #" << trixel; } /* Swap Bytes when required */ if (starReader.getByteSwap()) byteSwap(&stardata); /* Initialize star with data just read. */ StarObject *star; #ifdef KSTARS_LITE star = &(SB->addStar(stardata)->star); #else star = SB->addStar(stardata); #endif if (star) { //KStarsData* data = KStarsData::Instance(); //star->EquatorialToHorizontal( data->lst(), data->geo()->lat() ); //if( star->getHDIndex() != 0 ) if (stardata.HD) m_CatalogNumber.insert(stardata.HD, star); } else { qCCritical(KSTARS) << "CODE ERROR: More unnamed static stars in trixel " << trixel << " than we allocated space for!"; } } } } else { for (Trixel i = 0; i < m_skyMesh->size(); ++i) { Trixel trixel = i; quint64 records = starReader.getRecordCount(i); std::shared_ptr SB(new StarBlock(records)); if (!SB.get()) qCCritical(KSTARS) << "Could not allocate new StarBlock to hold shallow unnamed stars for trixel " << trixel; m_starBlockList.at(trixel)->setStaticBlock(SB); for (quint64 j = 0; j < records; ++j) { bool fread_success = false; fread_success = fread(&deepstardata, sizeof(DeepStarData), 1, dataFile); if (!fread_success) { qCCritical(KSTARS) << "Could not read StarData structure for star #" << j << " under trixel #" << trixel; } /* Swap Bytes when required */ if (starReader.getByteSwap()) byteSwap(&deepstardata); /* Initialize star with data just read. */ StarObject *star; #ifdef KSTARS_LITE star = &(SB->addStar(stardata)->star); #else star = SB->addStar(deepstardata); #endif if (star) { //KStarsData* data = KStarsData::Instance(); //star->EquatorialToHorizontal( data->lst(), data->geo()->lat() ); //if( star->getHDIndex() != 0 ) if (stardata.HD) m_CatalogNumber.insert(stardata.HD, star); } else { qCCritical(KSTARS) << "CODE ERROR: More unnamed static stars in trixel " << trixel << " than we allocated space for!"; } } } } return true; } bool DeepStarComponent::selected() { return Options::showStars() && fileOpened; } bool openIndexFile() { // TODO: Work out the details /* if( hdidxReader.openFile( "Henry-Draper.idx" ) ) qDebug() << "Could not open HD Index file. Search by HD numbers for deep stars will not work." << endl; */ return 0; } //This function is empty for a reason; we override the normal //update function in favor of JiT updates for stars. void DeepStarComponent::update(KSNumbers *) { } // TODO: Optimize draw, if it is worth it. void DeepStarComponent::draw(SkyPainter *skyp) { #ifndef KSTARS_LITE if (!fileOpened) return; #ifdef PROFILE_SINCOS long trig_calls_here = -dms::trig_function_calls; long trig_redundancy_here = -dms::redundant_trig_function_calls; long cachingdms_bad_uses = -CachingDms::cachingdms_bad_uses; dms::seconds_in_trig = 0.; #endif #ifdef PROFILE_UPDATECOORDS StarObject::updateCoordsCpuTime = 0.; StarObject::starsUpdated = 0; #endif SkyMap *map = SkyMap::Instance(); KStarsData *data = KStarsData::Instance(); UpdateID updateID = data->updateID(); //FIXME_FOV -- maybe not clamp like that... float radius = map->projector()->fov(); if (radius > 90.0) radius = 90.0; if (m_skyMesh != SkyMesh::Instance() && m_skyMesh->inDraw()) { printf("Warning: aborting concurrent DeepStarComponent::draw()"); } bool checkSlewing = (map->isSlewing() && Options::hideOnSlew()); //shortcuts to inform whether to draw different objects bool hideFaintStars(checkSlewing && Options::hideStars()); double hideStarsMag = Options::magLimitHideStar(); //adjust maglimit for ZoomLevel // double lgmin = log10(MINZOOM); // double lgmax = log10(MAXZOOM); // double lgz = log10(Options::zoomFactor()); // TODO: Enable hiding of faint stars float maglim = StarComponent::zoomMagnitudeLimit(); if (maglim < triggerMag) return; m_zoomMagLimit = maglim; m_skyMesh->inDraw(true); SkyPoint *focus = map->focus(); m_skyMesh->aperture(focus, radius + 1.0, DRAW_BUF); // divide by 2 for testing MeshIterator region(m_skyMesh, DRAW_BUF); // If we are to hide the fainter stars (eg: while slewing), we set the magnitude limit to hideStarsMag. if (hideFaintStars && maglim > hideStarsMag) maglim = hideStarsMag; StarBlockFactory *m_StarBlockFactory = StarBlockFactory::Instance(); // m_StarBlockFactory->drawID = m_skyMesh->drawID(); // qDebug() << "Mesh size = " << m_skyMesh->size() << "; drawID = " << m_skyMesh->drawID(); QTime t; int nTrixels = 0; t_dynamicLoad = 0; t_updateCache = 0; t_drawUnnamed = 0; visibleStarCount = 0; t.start(); // Mark used blocks in the LRU Cache. Not required for static stars if (!staticStars) { while (region.hasNext()) { Trixel currentRegion = region.next(); for (int i = 0; i < m_starBlockList.at(currentRegion)->getBlockCount(); ++i) { std::shared_ptr prevBlock = ((i >= 1) ? m_starBlockList.at(currentRegion)->block(i - 1) : std::shared_ptr()); std::shared_ptr block = m_starBlockList.at(currentRegion)->block(i); if (i == 0 && !m_StarBlockFactory->markFirst(block)) qCWarning(KSTARS) << "markFirst failed in trixel" << currentRegion; if (i > 0 && !m_StarBlockFactory->markNext(prevBlock, block)) qCWarning(KSTARS) << "markNext failed in trixel" << currentRegion << "while marking block" << i; if (i < m_starBlockList.at(currentRegion)->getBlockCount() && m_starBlockList.at(currentRegion)->block(i)->getFaintMag() < maglim) break; } } t_updateCache = t.restart(); region.reset(); } while (region.hasNext()) { ++nTrixels; Trixel currentRegion = region.next(); // NOTE: We are guessing that the last 1.5/16 magnitudes in the catalog are just additions and the star catalog // is actually supposed to reach out continuously enough only to mag m_FaintMagnitude * ( 1 - 1.5/16 ) // TODO: Is there a better way? We may have to change the magnitude tolerance if the catalog changes // Static stars need not execute fillToMag // Safety check if the current region is in star block list if ((int)currentRegion >= m_starBlockList.size()) continue; if (!staticStars && !m_starBlockList.at(currentRegion)->fillToMag(maglim) && maglim <= m_FaintMagnitude * (1 - 1.5 / 16)) { qCWarning(KSTARS) << "SBL::fillToMag( " << maglim << " ) failed for trixel " << currentRegion; } t_dynamicLoad += t.restart(); // qDebug() << "Drawing SBL for trixel " << currentRegion << ", SBL has " // << m_starBlockList[ currentRegion ]->getBlockCount() << " blocks" << endl; // REMARK: The following should never carry state, except for const parameters like updateID and maglim std::function)> mapFunction = [&updateID, &maglim](std::shared_ptr myBlock) { for (StarObject &star : myBlock->contents()) { if (star.updateID != updateID) star.JITupdate(); if (star.mag() > maglim) break; } }; QtConcurrent::blockingMap(m_starBlockList.at(currentRegion)->contents(), mapFunction); for (int i = 0; i < m_starBlockList.at(currentRegion)->getBlockCount(); ++i) { std::shared_ptr block = m_starBlockList.at(currentRegion)->block(i); // qDebug() << "---> Drawing stars from block " << i << " of trixel " << // currentRegion << ". SB has " << block->getStarCount() << " stars" << endl; for (int j = 0; j < block->getStarCount(); j++) { StarObject *curStar = block->star(j); // qDebug() << "We claim that he's from trixel " << currentRegion //<< ", and indexStar says he's from " << m_skyMesh->indexStar( curStar ); float mag = curStar->mag(); if (mag > maglim) break; if (skyp->drawPointSource(curStar, mag, curStar->spchar())) visibleStarCount++; } } // DEBUG: Uncomment to identify problems with Star Block Factory / preservation of Magnitude Order in the LRU Cache // verifySBLIntegrity(); t_drawUnnamed += t.restart(); } m_skyMesh->inDraw(false); #ifdef PROFILE_SINCOS trig_calls_here += dms::trig_function_calls; trig_redundancy_here += dms::redundant_trig_function_calls; cachingdms_bad_uses += CachingDms::cachingdms_bad_uses; qDebug() << "Spent " << dms::seconds_in_trig << " seconds doing " << trig_calls_here << " trigonometric function calls amounting to an average of " << 1000.0 * dms::seconds_in_trig / double(trig_calls_here) << " ms per call"; qDebug() << "Redundancy of trig calls in this draw: " << double(trig_redundancy_here) / double(trig_calls_here) * 100. << "%"; qDebug() << "CachedDms constructor calls so far: " << CachingDms::cachingdms_constructor_calls; qDebug() << "Caching has prevented " << CachingDms::cachingdms_delta << " redundant trig function calls"; qDebug() << "Bad cache uses in this draw: " << cachingdms_bad_uses; #endif #ifdef PROFILE_UPDATECOORDS qDebug() << "Spent " << StarObject::updateCoordsCpuTime << " seconds updating " << StarObject::starsUpdated << " stars' coordinates (StarObject::updateCoords) for an average of " << double(StarObject::updateCoordsCpuTime) / double(StarObject::starsUpdated) * 1.e6 << " us per star."; #endif #else Q_UNUSED(skyp) #endif } bool DeepStarComponent::openDataFile() { if (starReader.getFileHandle()) return true; starReader.openFile(dataFileName); fileOpened = false; if (!starReader.getFileHandle()) qCWarning(KSTARS) << "Failed to open deep star catalog " << dataFileName << ". Disabling it."; else if (!starReader.readHeader()) qCWarning(KSTARS) << "Header read error for deep star catalog " << dataFileName << "!! Disabling it!" << endl; else { qint16 faintmag; quint8 htm_level; int ret = 0; ret = fread(&faintmag, 2, 1, starReader.getFileHandle()); if (starReader.getByteSwap()) faintmag = bswap_16(faintmag); if (starReader.guessRecordSize() == 16) m_FaintMagnitude = faintmag / 1000.0; else m_FaintMagnitude = faintmag / 100.0; ret = fread(&htm_level, 1, 1, starReader.getFileHandle()); qCInfo(KSTARS) << "Processing " << dataFileName << ", HTMesh Level" << htm_level; m_skyMesh = SkyMesh::Instance(htm_level); if (!m_skyMesh) { if (!(m_skyMesh = SkyMesh::Create(htm_level))) { qCWarning(KSTARS) << "Could not create HTMesh of level " << htm_level << " for catalog " << dataFileName << ". Skipping it."; return false; } } ret = fread(&MSpT, 2, 1, starReader.getFileHandle()); if (starReader.getByteSwap()) MSpT = bswap_16(MSpT); fileOpened = true; qCInfo(KSTARS) << " Sky Mesh Size: " << m_skyMesh->size(); for (long int i = 0; i < m_skyMesh->size(); i++) { std::shared_ptr sbl(new StarBlockList(i, this)); if (!sbl.get()) { qCWarning(KSTARS) << "nullptr starBlockList. Expect trouble!"; } m_starBlockList.append(sbl); } m_zoomMagLimit = 0.06; } return fileOpened; } StarObject *DeepStarComponent::findByHDIndex(int HDnum) { // Currently, we only handle HD catalog indexes return m_CatalogNumber.value(HDnum, nullptr); // TODO: Maybe, make this more general. } // This uses the main star index for looking up nearby stars but then // filters out objects with the generic name "star". We could easily // build an index for just the named stars which would make this go // much faster still. -jbb // SkyObject *DeepStarComponent::objectNearest(SkyPoint *p, double &maxrad) { - StarObject *oBest = 0; + StarObject *oBest = nullptr; #ifdef KSTARS_LITE m_zoomMagLimit = StarComponent::zoomMagnitudeLimit(); #endif if (!fileOpened) return nullptr; m_skyMesh->index(p, maxrad + 1.0, OBJ_NEAREST_BUF); MeshIterator region(m_skyMesh, OBJ_NEAREST_BUF); while (region.hasNext()) { Trixel currentRegion = region.next(); // Safety check if the current region is in star block list if ((int)currentRegion >= m_starBlockList.size()) continue; for (int i = 0; i < m_starBlockList.at(currentRegion)->getBlockCount(); ++i) { std::shared_ptr block = m_starBlockList.at(currentRegion)->block(i); for (int j = 0; j < block->getStarCount(); ++j) { #ifdef KSTARS_LITE StarObject *star = &(block->star(j)->star); #else StarObject *star = block->star(j); #endif if (!star) continue; if (star->mag() > m_zoomMagLimit) continue; double r = star->angularDistanceTo(p).Degrees(); if (r < maxrad) { oBest = star; maxrad = r; } } } } // TODO: What if we are looking around a point that's not on // screen? objectNearest() will need to keep on filling up all // trixels around the SkyPoint to find the best match in case it // has not been found. Ideally, this should be implemented in a // different method and should be called after all other // candidates (eg: DeepSkyObject::objectNearest()) have been // called. return oBest; } bool DeepStarComponent::starsInAperture(QList &list, const SkyPoint ¢er, float radius, float maglim) { if (maglim < triggerMag) return false; // For DeepStarComponents, whether we use ra0() and dec0(), or // ra() and dec(), should not matter, because the stars are // repeated in all trixels that they will pass through, although // the factuality of this statement needs to be verified // Ensure that we have deprecessed the (RA, Dec) to (RA0, Dec0) Q_ASSERT(center.ra0().Degrees() >= 0.0); Q_ASSERT(center.dec0().Degrees() <= 90.0); m_skyMesh->intersect(center.ra0().Degrees(), center.dec0().Degrees(), radius, (BufNum)OBJ_NEAREST_BUF); MeshIterator region(m_skyMesh, OBJ_NEAREST_BUF); if (maglim < -28) maglim = m_FaintMagnitude; while (region.hasNext()) { Trixel currentRegion = region.next(); // FIXME: Build a better way to iterate over all stars. // Ideally, StarBlockList should have such a facility. std::shared_ptr sbl = m_starBlockList[currentRegion]; sbl->fillToMag(maglim); for (int i = 0; i < sbl->getBlockCount(); ++i) { std::shared_ptr block = sbl->block(i); for (int j = 0; j < block->getStarCount(); ++j) { #ifdef KSTARS_LITE StarObject *star = &(block->star(j)->star); #else StarObject *star = block->star(j); #endif if (star->mag() > maglim) break; // Stars are organized by magnitude, so this should work if (star->angularDistanceTo(¢er).Degrees() <= radius) list.append(star); } } } return true; } void DeepStarComponent::byteSwap(DeepStarData *stardata) { stardata->RA = bswap_32(stardata->RA); stardata->Dec = bswap_32(stardata->Dec); stardata->dRA = bswap_16(stardata->dRA); stardata->dDec = bswap_16(stardata->dDec); stardata->B = bswap_16(stardata->B); stardata->V = bswap_16(stardata->V); } void DeepStarComponent::byteSwap(StarData *stardata) { stardata->RA = bswap_32(stardata->RA); stardata->Dec = bswap_32(stardata->Dec); stardata->dRA = bswap_32(stardata->dRA); stardata->dDec = bswap_32(stardata->dDec); stardata->parallax = bswap_32(stardata->parallax); stardata->HD = bswap_32(stardata->HD); stardata->mag = bswap_16(stardata->mag); stardata->bv_index = bswap_16(stardata->bv_index); } bool DeepStarComponent::verifySBLIntegrity() { float faintMag = -5.0; bool integrity = true; for (Trixel trixel = 0; trixel < m_skyMesh->size(); ++trixel) { for (int i = 0; i < m_starBlockList[trixel]->getBlockCount(); ++i) { std::shared_ptr block = m_starBlockList[trixel]->block(i); if (i == 0) faintMag = block->getBrightMag(); // NOTE: Assumes 2 decimal places in magnitude field. TODO: Change if it ever does change if (block->getBrightMag() != faintMag && (block->getBrightMag() - faintMag) > 0.5) { qCWarning(KSTARS) << "Trixel " << trixel << ": ERROR: faintMag of prev block = " << faintMag << ", brightMag of block #" << i << " = " << block->getBrightMag(); integrity = false; } if (i > 1 && (!block->prev)) qCWarning(KSTARS) << "Trixel " << trixel << ": ERROR: Block" << i << "is unlinked in LRU Cache"; if (block->prev && block->prev->parent == m_starBlockList[trixel].get() && block->prev != m_starBlockList[trixel]->block(i - 1)) { qCWarning(KSTARS) << "Trixel " << trixel << ": ERROR: SBF LRU Cache linked list seems to be broken at before block " << i << endl; integrity = false; } faintMag = block->getFaintMag(); } } return integrity; } diff --git a/kstars/skycomponents/flagcomponent.h b/kstars/skycomponents/flagcomponent.h index 420cf3030..79104c583 100644 --- a/kstars/skycomponents/flagcomponent.h +++ b/kstars/skycomponents/flagcomponent.h @@ -1,185 +1,185 @@ /*************************************************************************** flagcomponent.h - K Desktop Planetarium ------------------- begin : Fri 16 Jan 2009 copyright : (C) 2009 by Jerome SONRIER email : jsid@emor3j.fr.eu.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 "pointlistcomponent.h" #include #include #include #include class SkyPainter; /** * @class FlagComponent * @short Represents a flag on the sky map. * Each flag is composed by a SkyPoint where coordinates are stored, an * epoch and a label. This class also stores flag images and associates * each flag with an image. * When FlagComponent is created, it seeks all file names beginning with * "_flag" in the user directory and *considere them as flag images. * * The file flags.dat stores coordinates, epoch, image name and label of each * flags and is read to init FlagComponent * * @author Jerome SONRIER * @version 1.1 */ class FlagComponent : public QObject, public PointListComponent { Q_OBJECT public: /** @short Constructor. */ explicit FlagComponent(SkyComposite *); /** @short Destructor. */ ~FlagComponent() override; void draw(SkyPainter *skyp) override; bool selected() override; - void update(KSNumbers *num = 0) override; + void update(KSNumbers *num = nullptr) override; /** * @short Add a flag. * @param SkyPoint Sky point in epoch coordinates * @param epoch Moment for which celestial coordinates are specified * @param image Image name * @param label Label of the flag */ void add(const SkyPoint &flagPoint, QString epoch, QString image, QString label, QColor labelColor); /** * @short Remove a flag. * @param index Index of the flag to be remove. */ void remove(int index); /** * @short Update a flag. * @param index index of the flag to be updated. * @param epoch new flag epoch. * @param image new flag image. * @param label new flag label. * @param labelColor new flag label color. */ void updateFlag(int index, const SkyPoint &flagPoint, QString epoch, QString image, QString label, QColor labelColor); /** * @short Return image names. * @return the list of all image names */ QStringList getNames(); /** * @short Return the numbers of flags. * @return the size of m_PointList */ int size(); /** * @short Get epoch. * @return the epoch as a string * @param index Index of the flag */ QString epoch(int index); /** * @short Get label. * @return the label as a string * @param index Index of the flag */ QString label(int index); /** * @short Get label color. * @return the label color * @param index Index of the flag */ QColor labelColor(int index); /** * @short Get image. * @return the image associated with the flag * @param index Index of the flag */ QImage image(int index); /** * @short Get image name. * @return the name of the image associated with the flag * @param index Index of the flag */ QString imageName(int index); /** * @short Get images. * @return all images that can be use */ QList imageList(); /** * @short Get image. * @param index Index of the image in m_Images * @return an image from m_Images */ QImage imageList(int index); /** * @brief epochCoords return coordinates recorded in original epoch * @param index index of the flag * @return pair of RA/DEC in original epoch */ QPair epochCoords(int index); /** * @short Get list of flag indexes near specified SkyPoint with radius specified in pixels. * @param point central SkyPoint. * @param pixelRadius radius in pixels. */ QList getFlagsNearPix(SkyPoint *point, int pixelRadius); /** @short Load flags from flags.dat file. */ void loadFromFile(); /** @short Save flags to flags.dat file. */ void saveToFile(); private: // Convert from given epoch to J2000. If epoch is already J2000, do nothing void toJ2000(SkyPoint *p, QString epoch); /// List of epochs QStringList m_Epoch; /// RA/DEC stored in original epoch QList> m_EpochCoords; /// List of image index QList m_FlagImages; /// List of label QStringList m_Labels; /// List of label colors QList m_LabelColors; /// List of image names QStringList m_Names; /// List of flag images QList m_Images; }; diff --git a/kstars/skycomponents/linelistindex.cpp b/kstars/skycomponents/linelistindex.cpp index b43786126..75449912a 100644 --- a/kstars/skycomponents/linelistindex.cpp +++ b/kstars/skycomponents/linelistindex.cpp @@ -1,269 +1,269 @@ /*************************************************************************** linelistindex.cpp - K Desktop Planetarium ------------------- begin : 2007-07-04 copyright : (C) 2007 by James B. Bowlin email : bowlin@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. * * * ***************************************************************************/ /**************************************************************************** * The filled polygon code in the innermost loops below in drawFilled*() below * implements the Sutherland-Hodgman's Polygon clipping algorithm. Please * don't mess with it unless you ensure that the Milky Way clipping continues * to work. The line clipping uses a similar but slightly less complicated * algorithm. * * Since the clipping code is a bit messy, there are four versions of the * inner loop for Filled/Outline * Integer/Float. This moved these two * decisions out of the inner loops to make them a bit faster and less * messy. * * -- James B. Bowlin * ****************************************************************************/ #include "linelistindex.h" #include "Options.h" #include "kstarsdata.h" #include "linelist.h" #ifndef KSTARS_LITE #include "skymap.h" #endif #include "skypainter.h" #include "htmesh/MeshIterator.h" LineListIndex::LineListIndex(SkyComposite *parent, const QString &name) : SkyComponent(parent), m_name(name) { m_skyMesh = SkyMesh::Instance(); m_lineIndex.reset(new LineListHash()); m_polyIndex.reset(new LineListHash()); } LineListIndex::~LineListIndex() { } // This is a callback for the indexLines() function below const IndexHash &LineListIndex::getIndexHash(LineList *lineList) { return skyMesh()->indexLine(lineList->points()); } void LineListIndex::removeLine(const std::shared_ptr &lineList) { const IndexHash &indexHash = getIndexHash(lineList.get()); IndexHash::const_iterator iter = indexHash.constBegin(); while (iter != indexHash.constEnd()) { Trixel trixel = iter.key(); iter++; if (m_lineIndex->contains(trixel)) m_lineIndex->value(trixel)->removeOne(lineList); } m_listList.removeOne(lineList); } void LineListIndex::appendLine(const std::shared_ptr &lineList) { const IndexHash &indexHash = getIndexHash(lineList.get()); IndexHash::const_iterator iter = indexHash.constBegin(); while (iter != indexHash.constEnd()) { Trixel trixel = iter.key(); iter++; if (!m_lineIndex->contains(trixel)) { m_lineIndex->insert(trixel, std::shared_ptr(new LineListList())); } m_lineIndex->value(trixel)->append(lineList); } m_listList.append(lineList); } void LineListIndex::appendPoly(const std::shared_ptr &lineList) { const IndexHash &indexHash = skyMesh()->indexPoly(lineList->points()); IndexHash::const_iterator iter = indexHash.constBegin(); while (iter != indexHash.constEnd()) { Trixel trixel = iter.key(); iter++; if (!m_polyIndex->contains(trixel)) { m_polyIndex->insert(trixel, std::shared_ptr(new LineListList())); } m_polyIndex->value(trixel)->append(lineList); } } void LineListIndex::appendBoth(const std::shared_ptr &lineList) { QMutexLocker m1(&mutex); appendLine(lineList); appendPoly(lineList); } void LineListIndex::reindexLines() { LineListHash *oldIndex = m_lineIndex.release(); DrawID drawID = skyMesh()->incDrawID(); m_lineIndex.reset(new LineListHash()); for (auto &listList : *oldIndex) { for (auto &item : *listList) { if (item->drawID == drawID) continue; item->drawID = drawID; appendLine(item); } listList.reset(); } delete oldIndex; } void LineListIndex::JITupdate(LineList *lineList) { KStarsData *data = KStarsData::Instance(); lineList->updateID = data->updateID(); SkyList *points = lineList->points(); if (lineList->updateNumID != data->updateNumID()) { lineList->updateNumID = data->updateNumID(); KSNumbers *num = data->updateNum(); for (int i = 0; i < points->size(); i++) { points->at(i)->updateCoords(num); } } for (int i = 0; i < points->size(); i++) { points->at(i)->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } } // This is a callback used in draw() below void LineListIndex::preDraw(SkyPainter *skyp) { skyp->setPen(QPen(QBrush(QColor("white")), 1, Qt::SolidLine)); } void LineListIndex::draw(SkyPainter *skyp) { if (!selected()) return; preDraw(skyp); drawLines(skyp); } #ifdef KSTARS_LITE MeshIterator LineListIndex::visibleTrixels() { return MeshIterator(skyMesh(), drawBuffer()); } #endif // This is a callback used int drawLinesInt() and drawLinesFloat() SkipHashList *LineListIndex::skipList(LineList *lineList) { Q_UNUSED(lineList) - return 0; + return nullptr; } void LineListIndex::drawLines(SkyPainter *skyp) { DrawID drawID = skyMesh()->drawID(); UpdateID updateID = KStarsData::Instance()->updateID(); for (auto &lineListList : m_lineIndex->values()) { for (int i = 0; i < lineListList->size(); i++) { std::shared_ptr lineList = lineListList->at(i); if (lineList->drawID == drawID) continue; lineList->drawID = drawID; if (lineList->updateID != updateID) JITupdate(lineList.get()); skyp->drawSkyPolyline(lineList.get(), skipList(lineList.get()), label()); } } } void LineListIndex::drawFilled(SkyPainter *skyp) { DrawID drawID = skyMesh()->drawID(); UpdateID updateID = KStarsData::Instance()->updateID(); MeshIterator region(skyMesh(), drawBuffer()); while (region.hasNext()) { std::shared_ptr lineListList = m_polyIndex->value(region.next()); - if (lineListList == 0) + if (lineListList == nullptr) continue; for (int i = 0; i < lineListList->size(); i++) { std::shared_ptr lineList = lineListList->at(i); // draw each Linelist at most once if (lineList->drawID == drawID) continue; lineList->drawID = drawID; if (lineList->updateID != updateID) JITupdate(lineList.get()); skyp->drawSkyPolygon(lineList.get()); } } } void LineListIndex::intro() { emitProgressText(i18n("Loading %1", m_name)); if (skyMesh()->debug() >= 1) qDebug() << QString("Loading %1 ...").arg(m_name); } void LineListIndex::summary() { if (skyMesh()->debug() < 2) return; int total = skyMesh()->size(); int polySize = m_polyIndex->size(); int lineSize = m_lineIndex->size(); if (lineSize > 0) printf("%4d out of %4d trixels in line index %3d%%\n", lineSize, total, 100 * lineSize / total); if (polySize > 0) printf("%4d out of %4d trixels in poly index %3d%%\n", polySize, total, 100 * polySize / total); } diff --git a/kstars/skycomponents/linelistindex.h b/kstars/skycomponents/linelistindex.h index 50734f850..8f012ba3c 100644 --- a/kstars/skycomponents/linelistindex.h +++ b/kstars/skycomponents/linelistindex.h @@ -1,188 +1,188 @@ /*************************************************************************** linelistindex.h - K Desktop Planetarium ------------------- begin : 2007-07-04 copyright : (C) 2007 James B. Bowlin email : bowlin@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 "skycomponent.h" #include "skymesh.h" #include #include #include class LineList; class LineListLabel; class SkipHashList; class SkyPainter; /** * @class LineListIndex * Contains almost all the code needed for indexing and drawing and clipping * lines and polygons. * * @author James B. Bowlin @version 0.1 */ class LineListIndex : public SkyComponent { friend class LinesItem; //Needs access to reindexLines public: /** * @short Constructor * Simply set the internal skyMesh, parent, and name. * @param parent Pointer to the parent SkyComponent object * @param mesh Pointer to the universal SkyMesh instance * @param name name of the subclass used for debugging */ explicit LineListIndex(SkyComposite *parent, const QString &name = ""); /** @short Destructor */ ~LineListIndex() override; /** * @short The top level draw routine. Draws all the LineLists for any * subclass in one fell swoop which minimizes some of the loop overhead. * Overridden by MilkWay so it can decide whether to draw outlines or * filled. Therefore MilkyWay does not need to override preDraw(). The * MilkyWay draw() routine calls all of the more specific draw() * routines below. */ void draw(SkyPainter *skyp) override; #ifdef KSTARS_LITE /** * @short KStars Lite needs direct access to m_lineIndex for drawing the lines */ inline LineListHash *lineIndex() const { return m_lineIndex.get(); } inline LineListHash *polyIndex() const { return m_polyIndex.get(); } /** @short returns MeshIterator for currently visible trixels */ MeshIterator visibleTrixels(); #endif //Moved to public because KStars Lite uses it /** * @short this is called from within the draw routines when the updateID * of the lineList is stale. It is virtual because different subclasses * have different update routines. NoPrecessIndex doesn't precess in * the updates and ConstellationLines must update its points as stars, * not points. that doesn't precess the points. */ virtual void JITupdate(LineList *lineList); protected: /** * @short as the name says, recreates the lineIndex using the LineLists * in the previous index. Since we are indexing everything at J2000 * this is only used by ConstellationLines which needs to reindex * because of the proper motion of the stars. */ void reindexLines(); /** @short retrieve name of object */ QString name() const { return m_name; } /** * @short displays a message that we are loading m_name. Also prints * out the message if skyMesh debug is greater than zero. */ void intro(); /** * @short prints out some summary statistics if the skyMesh debug is * greater than 1. */ void summary(); /** @short Returns the SkyMesh object. */ SkyMesh *skyMesh() { return m_skyMesh; } /** * @short Typically called from within a subclasses constructors. * Adds the trixels covering the outline of lineList to the lineIndex. */ void appendLine(const std::shared_ptr &lineList); void removeLine(const std::shared_ptr &lineList); /** * @short Typically called from within a subclasses constructors. * Adds the trixels covering the full lineList to the polyIndex. */ void appendPoly(const std::shared_ptr &lineList); /** * @short a convenience method that adds a lineList to both the lineIndex and the polyIndex. */ void appendBoth(const std::shared_ptr &lineList); /** * @short Draws all the lines in m_listList as simple lines in float mode. */ void drawLines(SkyPainter *skyp); /** * @short Draws all the lines in m_listList as filled polygons in float * mode. */ void drawFilled(SkyPainter *skyp); /** * @short Gives the subclasses access to the top of the draw() method. * Typically used for setting the QPen, etc. in the QPainter being * passed in. Defaults to setting a thin white pen. */ virtual void preDraw(SkyPainter *skyp); /** * @short a callback overridden by NoPrecessIndex so it can use the * drawing code with the non-reverse-precessed mesh buffer. */ virtual MeshBufNum_t drawBuffer() { return DRAW_BUF; } /** * @short Returns an IndexHash from the SkyMesh that contains the set of * trixels that cover lineList. Overridden by SkipListIndex so it can * pass SkyMesh an IndexHash indicating which line segments should not * be indexed @param lineList contains the list of points to be covered. */ virtual const IndexHash &getIndexHash(LineList *lineList); /** * @short Also overridden by SkipListIndex. * Controls skipping inside of the draw() routines. The default behavior * is to simply return a null pointer. * * FIXME: I don't think that the SkipListIndex class even exists -- hdevalence */ virtual SkipHashList *skipList(LineList *lineList); - virtual LineListLabel *label() { return 0; } + virtual LineListLabel *label() { return nullptr; } inline LineListList listList() const { return m_listList; } private: QString m_name; SkyMesh *m_skyMesh { nullptr }; std::unique_ptr m_lineIndex; std::unique_ptr m_polyIndex; LineListList m_listList; QMutex mutex; }; diff --git a/kstars/skycomponents/linelistlabel.cpp b/kstars/skycomponents/linelistlabel.cpp index c926e537d..5781194da 100644 --- a/kstars/skycomponents/linelistlabel.cpp +++ b/kstars/skycomponents/linelistlabel.cpp @@ -1,207 +1,207 @@ /*************************************************************************** linelistlabel.cpp - K Desktop Planetarium ------------------- begin : 2007-08-08 copyright : (C) 2007 James B. Bowlin email : bowlin@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. * * * ***************************************************************************/ #include "linelistlabel.h" #include "linelist.h" #include "Options.h" #ifndef KSTARS_LITE #include "skymap.h" #endif #include "skylabeler.h" #include "projections/projector.h" LineListLabel::LineListLabel(const QString &text) : m_text(text) { m_skyLabeler = SkyLabeler::Instance(); // prevent a crash if drawGuideLabel() is called before reset() for (int i = 0; i < 4; i++) { m_labList[i] = nullptr; m_labIndex[i] = 0; } } void LineListLabel::reset() { // These are the indices of the farthest left point, farthest right point, // etc. The are data members so the drawLabels() routine can use them. // Zero indicates an index that was never set and is considered invalid // inside of drawLabels(). for (int i = 0; i < 4; i++) { - m_labList[i] = 0; + m_labList[i] = nullptr; m_labIndex[i] = 0; } // These are used to keep track of the element that is farthest left, // farthest right, etc. m_farLeft = 100000.0; m_farRight = 0.0; m_farTop = 100000.0; m_farBot = 0.0; m_skyLabeler->getMargins(m_text, &m_marginLeft, &m_marginRight, &m_marginTop, &m_marginBot); } void LineListLabel::updateLabelCandidates(qreal x, qreal y, LineList *lineList, int i) { if (i < 1) return; if (x < m_marginLeft || x > m_marginRight || y < m_marginTop || y > m_marginBot) return; if (x < m_farLeft) { m_labIndex[LeftCandidate] = i; m_labList[LeftCandidate] = lineList; m_farLeft = x; } if (x > m_farRight) { m_labIndex[RightCandidate] = i; m_labList[RightCandidate] = lineList; m_farRight = x; } if (y > m_farBot) { m_labIndex[BotCandidate] = i; m_labList[BotCandidate] = lineList; m_farBot = x; } if (y < m_farTop) { m_labIndex[TopCandidate] = i; m_labList[TopCandidate] = lineList; m_farTop = x; } } void LineListLabel::draw() { #ifndef KSTARS_LITE const Projector *proj = SkyMap::Instance()->projector(); double comfyAngle = 40.0; // the first valid candidate with an angle // smaller than this gets displayed. If you set // this to > 90. then the first valid candidate // will be displayed, regardless of angle. // We store info about the four candidate points in arrays to make several // of the steps easier, particularly choosing the valid candidate with the // smallest angle from the horizontal. int idx[4]; // index of candidate LineList *list[4]; // LineList of candidate double a[4] = { 360.0, 360.0, 360.0, 360.0 }; // angle, default to large value QPointF o[4]; // candidate point bool okay[4] = { true, true, true, true }; // flag candidate false if it // overlaps a previous label. // We no longer adjust the order but if we were to it would be here static int Order[4] = { LeftCandidate, BotCandidate, TopCandidate, LeftCandidate }; for (int j = 0; j < 4; j++) { idx[j] = m_labIndex[Order[j]]; list[j] = m_labList[Order[j]]; } // Make sure start with a valid candidate int first = 0; for (; first < 4; first++) { if (idx[first]) break; } // return if there are no valid candidates if (first >= 4) return; // Try the points in order and print the label if we can draw it at // a comfortable angle for viewing; for (int j = first; j < 4; j++) { o[j] = angleAt(proj, list[j], idx[j], &a[j]); if (!idx[j] || !proj->checkVisibility(list[j]->at(idx[j]).get())) { okay[j] = false; continue; } if (fabs(a[j]) > comfyAngle) continue; if (m_skyLabeler->drawGuideLabel(o[j], m_text, a[j])) return; okay[j] = false; } //--- No angle was comfy so pick the one with the smallest angle --- // Index of the index/angle/point that gets displayed int best = first; // find first valid candidate that does not overlap existing labels for (; best < 4; best++) { if (idx[best] && okay[best]) break; } // return if all candiates either overlap or are invalid if (best >= 4) return; // find the valid non-overlap candidate with the smallest angle for (int j = best + 1; j < 4; j++) { if (idx[j] && okay[j] && fabs(a[j]) < fabs(a[best])) best = j; } m_skyLabeler->drawGuideLabel(o[best], m_text, a[best]); #endif } QPointF LineListLabel::angleAt(const Projector *proj, LineList *list, int i, double *angle) { const SkyPoint *pThis = list->at(i).get(); const SkyPoint *pLast = list->at(i-1).get(); QPointF oThis = proj->toScreen(pThis); QPointF oLast = proj->toScreen(pLast); double sx = double(oThis.x() - oLast.x()); double sy = double(oThis.y() - oLast.y()); *angle = atan2(sy, sx) * 180.0 / dms::PI; // FIXME: use clamp in KSUtils // Never draw the label upside down if (*angle < -90.0) *angle += 180.0; if (*angle > 90.0) *angle -= 180.0; return oThis; } diff --git a/kstars/skycomponents/listcomponent.cpp b/kstars/skycomponents/listcomponent.cpp index a2e252f56..f36a1e14f 100644 --- a/kstars/skycomponents/listcomponent.cpp +++ b/kstars/skycomponents/listcomponent.cpp @@ -1,88 +1,88 @@ /*************************************************************************** listcomponent.cpp - K Desktop Planetarium ------------------- begin : 2005/10/01 copyright : (C) 2005 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 "listcomponent.h" #include "kstarsdata.h" #ifndef KSTARS_LITE #include "skymap.h" #endif ListComponent::ListComponent(SkyComposite *parent) : SkyComponent(parent) { } ListComponent::~ListComponent() { qDeleteAll(m_ObjectList); m_ObjectList.clear(); clear(); } void ListComponent::clear() { while (!m_ObjectList.isEmpty()) { SkyObject *o = m_ObjectList.takeFirst(); removeFromNames(o); delete o; } } void ListComponent::update(KSNumbers *num) { if (!selected()) return; KStarsData *data = KStarsData::Instance(); foreach (SkyObject *o, m_ObjectList) { if (num) o->updateCoords(num); o->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } } SkyObject *ListComponent::findByName(const QString &name) { foreach (SkyObject *o, m_ObjectList) { if (QString::compare(o->name(), name, Qt::CaseInsensitive) == 0 || QString::compare(o->longname(), name, Qt::CaseInsensitive) == 0 || QString::compare(o->name2(), name, Qt::CaseInsensitive) == 0) return o; } //No object found - return 0; + return nullptr; } SkyObject *ListComponent::objectNearest(SkyPoint *p, double &maxrad) { if (!selected()) - return 0; + return nullptr; - SkyObject *oBest = 0; + SkyObject *oBest = nullptr; foreach (SkyObject *o, m_ObjectList) { double r = o->angularDistanceTo(p).Degrees(); if (r < maxrad) { oBest = o; maxrad = r; } } return oBest; } diff --git a/kstars/skycomponents/listcomponent.h b/kstars/skycomponents/listcomponent.h index b1b4fc5f0..7e92e7f6f 100644 --- a/kstars/skycomponents/listcomponent.h +++ b/kstars/skycomponents/listcomponent.h @@ -1,64 +1,64 @@ /*************************************************************************** listcomponent.h - K Desktop Planetarium ------------------- begin : 2005/10/01 copyright : (C) 2005 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. * * * ***************************************************************************/ #pragma once #include "skycomponent.h" #include class SkyComposite; class SkyMap; /** * @class ListComponent * An abstract parent class, to be inherited by SkyComponents that store a QList of SkyObjects. * * @author Jason Harris * @version 0.1 */ class ListComponent : public SkyComponent { public: explicit ListComponent(SkyComposite *parent); ~ListComponent() override; /** * @short Update the sky positions of this component. * * This function usually just updates the Horizontal (Azimuth/Altitude) * coordinates of the objects in this component. If the KSNumbers* * argument is not nullptr, this function also recomputes precession and * nutation for the date in KSNumbers. * @p num Pointer to the KSNumbers object * @note By default, the num parameter is nullptr, indicating that * Precession/Nutation computation should be skipped; this computation * is only occasionally required. */ - void update(KSNumbers *num = 0) override; + void update(KSNumbers *num = nullptr) override; SkyObject *findByName(const QString &name) override; SkyObject *objectNearest(SkyPoint *p, double &maxrad) override; void clear(); const QList &objectList() const { return m_ObjectList; } protected: QList m_ObjectList; }; diff --git a/kstars/skycomponents/planetmoonscomponent.cpp b/kstars/skycomponents/planetmoonscomponent.cpp index 591c6db60..3b6946bdd 100644 --- a/kstars/skycomponents/planetmoonscomponent.cpp +++ b/kstars/skycomponents/planetmoonscomponent.cpp @@ -1,174 +1,174 @@ /*************************************************************************** planetmoonscomponent.cpp - K Desktop Planetarium ------------------- begin : Sat Mar 13 2009 copyright : (C) 2009 by Vipul Kumar Singh, Médéric Boquien email : vipulkrsingh@gmail.com, mboquien@free.fr ***************************************************************************/ /*************************************************************************** * * * 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 "planetmoonscomponent.h" #include "Options.h" #include "kstarsdata.h" #include "skylabeler.h" #include "skypainter.h" #include "solarsystemcomposite.h" #include "solarsystemsinglecomponent.h" #include "projections/projector.h" #include "skyobjects/jupitermoons.h" PlanetMoonsComponent::PlanetMoonsComponent(SkyComposite *p, SolarSystemSingleComponent *planetComponent, KSPlanetBase::Planets& _planet) : SkyComponent(p), planet(_planet), m_Planet(planetComponent) { /* if (planet == KSPlanetBase::JUPITER) pmoons = new JupiterMoons(); else pmoons = new SaturnMoons(); */ Q_ASSERT(planet == KSPlanetBase::JUPITER); // delete pmoons; // pmoons = new JupiterMoons(); // int nmoons = pmoons->nMoons(); // for (int i = 0; i < nmoons; ++i) // { // objectNames(SkyObject::MOON).append( pmoons->name(i) ); // objectLists(SkyObject::MOON).append( QPair(pmoons->name(i),pmoons->moon(i)) ); // } } PlanetMoonsComponent::~PlanetMoonsComponent() { } bool PlanetMoonsComponent::selected() { return m_Planet->selected(); } #ifndef KSTARS_LITE void PlanetMoonsComponent::update(KSNumbers *) { KStarsData *data = KStarsData::Instance(); if (selected()) pmoons->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } #endif void PlanetMoonsComponent::updateMoons(KSNumbers *num) { //FIXME: evil cast if (selected()) pmoons->findPosition(num, m_Planet->planet(), (KSSun *)(parent()->findByName("Sun"))); } SkyObject *PlanetMoonsComponent::findByName(const QString &name) { int nmoons = pmoons->nMoons(); for (int i = 0; i < nmoons; ++i) { TrailObject *moon = pmoons->moon(i); if (QString::compare(moon->name(), name, Qt::CaseInsensitive) == 0 || QString::compare(moon->longname(), name, Qt::CaseInsensitive) == 0 || QString::compare(moon->name2(), name, Qt::CaseInsensitive) == 0) return moon; } - return 0; + return nullptr; } #ifdef KSTARS_LITE KSPlanetBase *PlanetMoonsComponent::getPlanet() const { return m_Planet->planet(); } #endif SkyObject *PlanetMoonsComponent::objectNearest(SkyPoint *p, double &maxrad) { SkyObject *oBest = nullptr; int nmoons = pmoons->nMoons(); if (Options::zoomFactor() < 3000) return nullptr; for (int i = 0; i < nmoons; ++i) { SkyObject *moon = pmoons->moon(i); double r = moon->angularDistanceTo(p).Degrees(); if (r < maxrad) { maxrad = r; oBest = moon; } } return oBest; } void PlanetMoonsComponent::draw(SkyPainter *skyp) { if (!(planet == KSPlanetBase::JUPITER && Options::showJupiter())) return; //In order to get the z-order right for the moons and the planet, //we need to first draw the moons that are further away than the planet, //then re-draw the planet, then draw the moons nearer than the planet. QList frontMoons; int nmoons = pmoons->nMoons(); for (int i = 0; i < nmoons; ++i) { if (pmoons->z(i) < 0.0) //Moon is nearer than the planet { frontMoons.append(pmoons->moon(i)); } else { //Draw Moons that are further than the planet skyp->drawPointSource(pmoons->moon(i), pmoons->moon(i)->mag()); } } //Now redraw the planet m_Planet->draw(skyp); //Now draw the remaining moons, as stored in frontMoons foreach (TrailObject *moon, frontMoons) { skyp->drawPointSource(moon, moon->mag()); } //Draw Moon name labels if at high zoom if (!(Options::showPlanetNames() && Options::zoomFactor() > 50. * MINZOOM)) return; for (int i = 0; i < nmoons; ++i) { /* if (planet ==KSPlanetBase::SATURN) SkyLabeler::AddLabel( pmoons->moon(i), SkyLabeler::SATURN_MOON_LABEL ); else */ SkyLabeler::AddLabel(pmoons->moon(i), SkyLabeler::JUPITER_MOON_LABEL); } } void PlanetMoonsComponent::drawTrails(SkyPainter *skyp) { if (!selected()) return; int nmoons = pmoons->nMoons(); for (int i = 0; i < nmoons; ++i) pmoons->moon(i)->drawTrail(skyp); } diff --git a/kstars/skycomponents/pointlistcomponent.h b/kstars/skycomponents/pointlistcomponent.h index 479cce870..5e36d4821 100644 --- a/kstars/skycomponents/pointlistcomponent.h +++ b/kstars/skycomponents/pointlistcomponent.h @@ -1,62 +1,62 @@ /*************************************************************************** pointlistcomponent.h - K Desktop Planetarium ------------------- begin : 2005/10/01 copyright : (C) 2005 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. * * * ***************************************************************************/ #pragma once #define NCIRCLE 360 //number of points used to define equator, ecliptic and horizon #include "skycomponent.h" #include #include class SkyPoint; /** * @class PointListComponent * An abstract parent class, to be inherited by SkyComponents that store a QList of SkyPoints. * * @author Jason Harris * @version 0.1 */ class PointListComponent : public SkyComponent { public: explicit PointListComponent(SkyComposite *parent); ~PointListComponent() override; /** * @short Update the sky positions of this component. * * This function usually just updates the Horizontal (Azimuth/Altitude) * coordinates of the objects in this component. However, the precession * and nutation must also be recomputed periodically. Requests to do * so are sent through the doPrecess parameter. * @p num Pointer to the KSNumbers object * @note By default, the num parameter is nullptr, indicating that * Precession/Nutation computation should be skipped; this computation * is only occasionally required. */ - void update(KSNumbers *num = 0) override; + void update(KSNumbers *num = nullptr) override; QList> &pointList() { return m_PointList; } private: QList> m_PointList; }; diff --git a/kstars/skycomponents/satellitescomponent.cpp b/kstars/skycomponents/satellitescomponent.cpp index 252acdfc1..48e997a8f 100644 --- a/kstars/skycomponents/satellitescomponent.cpp +++ b/kstars/skycomponents/satellitescomponent.cpp @@ -1,260 +1,260 @@ /*************************************************************************** satellitescomponent.cpp - K Desktop Planetarium ------------------- begin : Tue 02 Mar 2011 copyright : (C) 2009 by Jerome SONRIER email : jsid@emor3j.fr.eu.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 "satellitescomponent.h" #include "ksfilereader.h" #include "ksnotification.h" #include "kstarsdata.h" #include "Options.h" #include "skylabeler.h" #include "skymap.h" #include "skypainter.h" #include "skyobjects/satellite.h" #include #include #include #include SatellitesComponent::SatellitesComponent(SkyComposite *parent) : SkyComponent(parent) { QtConcurrent::run(this, &SatellitesComponent::loadData); } SatellitesComponent::~SatellitesComponent() { qDeleteAll(m_groups); m_groups.clear(); } void SatellitesComponent::loadData() { KSFileReader fileReader; QString line; QStringList group_infos; if (!fileReader.open("satellites.dat")) return; emitProgressText(i18n("Loading satellites")); while (fileReader.hasMoreLines()) { line = fileReader.readLine(); if (line.trimmed().isEmpty() || line.at(0) == '#') continue; group_infos = line.split(';'); m_groups.append(new SatelliteGroup(group_infos.at(0), group_infos.at(1), QUrl(group_infos.at(2)))); } objectNames(SkyObject::SATELLITE).clear(); objectLists(SkyObject::SATELLITE).clear(); foreach (SatelliteGroup *group, m_groups) { for (int i = 0; i < group->size(); i++) { Satellite *sat = group->at(i); if (sat->selected() && nameHash.contains(sat->name().toLower()) == false) { objectNames(SkyObject::SATELLITE).append(sat->name()); objectLists(SkyObject::SATELLITE).append(QPair(sat->name(), sat)); nameHash[sat->name().toLower()] = sat; } } } } bool SatellitesComponent::selected() { return Options::showSatellites(); } void SatellitesComponent::update(KSNumbers *) { // Return if satellites must not be draw if (!selected()) return; foreach (SatelliteGroup *group, m_groups) { group->updateSatellitesPos(); } } void SatellitesComponent::draw(SkyPainter *skyp) { #ifndef KSTARS_LITE // Return if satellites must not be draw if (!selected()) return; bool hideLabels = (!Options::showSatellitesLabels() || (SkyMap::Instance()->isSlewing() && Options::hideLabels())); foreach (SatelliteGroup *group, m_groups) { for (int i = 0; i < group->size(); i++) { Satellite *sat = group->at(i); if (sat->selected()) { bool drawn = false; if (Options::showVisibleSatellites()) { if (sat->isVisible()) drawn = skyp->drawSatellite(sat); } else { drawn = skyp->drawSatellite(sat); } if (drawn && !hideLabels) SkyLabeler::AddLabel(sat, SkyLabeler::SATELLITE_LABEL); } } } #else Q_UNUSED(skyp); #endif } void SatellitesComponent::drawLabel(Satellite *sat, const QPointF& pos) { SkyLabeler *labeler = SkyLabeler::Instance(); labeler->setPen(KStarsData::Instance()->colorScheme()->colorNamed("SatLabelColor")); labeler->drawNameLabel(sat, pos); } void SatellitesComponent::drawTrails(SkyPainter *skyp) { Q_UNUSED(skyp); } void SatellitesComponent::updateTLEs() { int i = 0; QProgressDialog progressDlg(i18n("Update TLEs..."), i18n("Abort"), 0, m_groups.count()); progressDlg.setWindowModality(Qt::WindowModal); progressDlg.setValue(0); foreach (SatelliteGroup *group, m_groups) { if (progressDlg.wasCanceled()) return; if (group->tleUrl().isEmpty()) continue; progressDlg.setLabelText(i18n("Update %1 satellites", group->name())); progressDlg.setWindowTitle(i18n("Satellite Orbital Elements Update")); QNetworkAccessManager manager; QNetworkReply *response = manager.get(QNetworkRequest(group->tleUrl())); // Wait synchronously QEventLoop event; QObject::connect(response, SIGNAL(finished()), &event, SLOT(quit())); event.exec(); if (response->error() == QNetworkReply::NoError) { QFile file(group->tleFilename().toLocalFile()); if (file.open(QFile::WriteOnly)) { file.write(response->readAll()); file.close(); group->readTLE(); group->updateSatellitesPos(); progressDlg.setValue(++i); } else { KSNotification::error(file.errorString()); return; } } else { KSNotification::error(response->errorString()); return; } } } QList SatellitesComponent::groups() { return m_groups; } Satellite *SatellitesComponent::findSatellite(QString name) { foreach (SatelliteGroup *group, m_groups) { for (int i = 0; i < group->size(); i++) { Satellite *sat = group->at(i); if (sat->name() == name) return sat; } } - return 0; + return nullptr; } SkyObject *SatellitesComponent::objectNearest(SkyPoint *p, double &maxrad) { if (!selected()) - return 0; + return nullptr; //KStarsData* data = KStarsData::Instance(); - SkyObject *oBest = 0; + SkyObject *oBest = nullptr; double rBest = maxrad; double r; foreach (SatelliteGroup *group, m_groups) { for (int i = 0; i < group->size(); i++) { Satellite *sat = group->at(i); if (!sat->selected()) continue; r = sat->angularDistanceTo(p).Degrees(); //qDebug() << sat->name(); //qDebug() << "r = " << r << " - max = " << rBest; //qDebug() << "ra2=" << sat->ra().Degrees() << " - dec2=" << sat->dec().Degrees(); if (r < rBest) { rBest = r; oBest = sat; } } } maxrad = rBest; return oBest; } SkyObject *SatellitesComponent::findByName(const QString &name) { return nameHash[name.toLower()]; } diff --git a/kstars/skycomponents/skycomponent.cpp b/kstars/skycomponents/skycomponent.cpp index 8dafe19cc..7c65045fc 100644 --- a/kstars/skycomponents/skycomponent.cpp +++ b/kstars/skycomponents/skycomponent.cpp @@ -1,104 +1,104 @@ /*************************************************************************** skycomponent.cpp - K Desktop Planetarium ------------------- begin : 2005/07/08 copyright : (C) 2005 by Thomas Kabelmann email : thomas.kabelmann@gmx.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. * * * ***************************************************************************/ #include "skycomponent.h" #include "Options.h" #include "skycomposite.h" #include "skyobjects/skyobject.h" SkyComponent::SkyComponent(SkyComposite *parent) : m_parent(parent) { } SkyComponent::~SkyComponent() { } //Hand the message up to SkyMapComposite void SkyComponent::emitProgressText(const QString &message) { parent()->emitProgressText(message); } SkyObject *SkyComponent::findByName(const QString &) { - return 0; + return nullptr; } SkyObject *SkyComponent::objectNearest(SkyPoint *, double &) { - return 0; + return nullptr; } void SkyComponent::drawTrails(SkyPainter *) { } void SkyComponent::objectsInArea(QList &, const SkyRegion &) { } QHash &SkyComponent::getObjectNames() { if (!parent()) { // Use a fake list if there is no parent object static QHash temp; return temp; } return parent()->objectNames(); } QHash>> &SkyComponent::getObjectLists() { if (!parent()) { // Use a fake list if there is no parent object static QHash>> temp; return temp; } return parent()->objectLists(); } void SkyComponent::removeFromNames(const SkyObject *obj) { QStringList &names = getObjectNames()[obj->type()]; int i; i = names.indexOf(obj->name()); if (i >= 0) names.removeAt(i); i = names.indexOf(obj->longname()); if (i >= 0) names.removeAt(i); } void SkyComponent::removeFromLists(const SkyObject *obj) { QVector> &names = getObjectLists()[obj->type()]; int i; i = names.indexOf(QPair(obj->name(), obj)); if (i >= 0) names.removeAt(i); i = names.indexOf(QPair(obj->longname(), obj)); if (i >= 0) names.removeAt(i); } diff --git a/kstars/skycomponents/skycomposite.cpp b/kstars/skycomponents/skycomposite.cpp index d9b0b3663..fa63aae28 100644 --- a/kstars/skycomponents/skycomposite.cpp +++ b/kstars/skycomponents/skycomposite.cpp @@ -1,105 +1,105 @@ /*************************************************************************** skycomposite.cpp - K Desktop Planetarium ------------------- begin : 2005/07/08 copyright : (C) 2005 by Thomas Kabelmann email : thomas.kabelmann@gmx.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. * * * ***************************************************************************/ #include "skycomposite.h" #include "skyobjects/skyobject.h" SkyComposite::SkyComposite(SkyComposite *parent) : SkyComponent(parent) { } SkyComposite::~SkyComposite() { qDeleteAll(components()); m_Components.clear(); } void SkyComposite::addComponent(SkyComponent *component, int priority) { //qDebug() << "Adding sky component " << component << " of type " << typeid( *component ).name() << " with priority " << priority; m_Components.insertMulti(priority, component); /* foreach( SkyComponent *p, components() ) { qDebug() << "List now has: " << p << " of type " << typeid( *p ).name(); } */ } void SkyComposite::removeComponent(SkyComponent *const component) { // qDebug() << "Removing sky component " << component << " of type " << typeid( *component ).name(); QMap::iterator it; for (it = m_Components.begin(); it != m_Components.end();) { if (it.value() == component) it = m_Components.erase(it); else ++it; } /* foreach( SkyComponent *p, components() ) { qDebug() << "List now has: " << p << " of type " << typeid( *p ).name(); } */ } void SkyComposite::draw(SkyPainter *skyp) { if (selected()) foreach (SkyComponent *component, components()) component->draw(skyp); } void SkyComposite::update(KSNumbers *num) { foreach (SkyComponent *component, components()) component->update(num); } SkyObject *SkyComposite::findByName(const QString &name) { foreach (SkyComponent *comp, components()) { SkyObject *o = comp->findByName(name); if (o) return o; } - return 0; + return nullptr; } SkyObject *SkyComposite::objectNearest(SkyPoint *p, double &maxrad) { if (!selected()) - return 0; - SkyObject *oBest = 0; + return nullptr; + SkyObject *oBest = nullptr; foreach (SkyComponent *comp, components()) { // qDebug() << "Checking " << typeid( *comp ).name() <<" for oBest"; SkyObject *oTry = comp->objectNearest(p, maxrad); if (oTry) { oBest = oTry; maxrad = p->angularDistanceTo(oBest).Degrees() * 0.95; // Set a new maxrad, smaller than original to give priority to the earlier objects in the list. // qDebug() << "oBest = " << oBest << " of type " << typeid( *oTry ).name() << "; updated maxrad = " << maxrad; } } // qDebug() << "Returning best match: oBest = " << oBest; return oBest; //will be 0 if no object nearer than maxrad was found } diff --git a/kstars/skycomponents/skycomposite.h b/kstars/skycomponents/skycomposite.h index 98dc9a198..eab366d59 100644 --- a/kstars/skycomponents/skycomposite.h +++ b/kstars/skycomponents/skycomposite.h @@ -1,112 +1,112 @@ /*************************************************************************** skycomposite.h - K Desktop Planetarium ------------------- begin : 2005/07/08 copyright : (C) 2005 by Thomas Kabelmann email : thomas.kabelmann@gmx.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 #include #include "skycomponent.h" class KSNumbers; /** * @class SkyComposite * SkyComposite is a kind of container class for SkyComponent objects. The SkyComposite is * responsible for distributing calls to functions like draw() and update() to its children, * which can be SkyComponents or other SkyComposites with their own children. This is based * on the "composite/component" design pattern. * * Performance tuning: Sometimes it will be better to override a virtual function and do * the work in the composite instead of delegating the request to all sub components. * * @author Thomas Kabelmann * @version 0.1 */ class SkyComposite : public SkyComponent { public: /** * @short Constructor * @p parent pointer to the parent SkyComponent */ explicit SkyComposite(SkyComposite *parent = nullptr); /** *@short Destructor */ ~SkyComposite() override; /** * @short Delegate draw requests to all sub components * @p psky Reference to the QPainter on which to paint */ void draw(SkyPainter *skyp) override; /** * @short Delegate update-position requests to all sub components * * This function usually just updates the Horizontal (Azimuth/Altitude) coordinates. * However, the precession and nutation must also be recomputed periodically. Requests to * do so are sent through the doPrecess parameter. * @p num Pointer to the KSNumbers object * @sa updatePlanets() * @sa updateMoons() * @note By default, the num parameter is nullptr, indicating that Precession/Nutation * computation should be skipped; this computation is only occasionally required. */ - void update(KSNumbers *num = 0) override; + void update(KSNumbers *num = nullptr) override; /** * @short Add a new sub component to the composite * @p comp Pointer to the SkyComponent to be added * @p priority A priority ordering for various operations on the list of all sky components * (notably objectNearest()) */ void addComponent(SkyComponent *comp, int priority = 1024); /** * @short Remove a sub component from the composite * @p comp Pointer to the SkyComponent to be removed. */ void removeComponent(SkyComponent *const comp); /** * @short Search the children of this SkyComposite for a SkyObject whose name matches * the argument. * * The objects' primary, secondary and long-form names will all be checked for a match. * @p name the name to be matched * @return a pointer to the SkyObject whose name matches * the argument, or a nullptr pointer if no match was found. */ SkyObject *findByName(const QString &name) override; /** * @short Identify the nearest SkyObject to the given SkyPoint, among the children of * this SkyComposite * @p p pointer to the SkyPoint around which to search. * @p maxrad reference to current search radius * @return a pointer to the nearest SkyObject */ SkyObject *objectNearest(SkyPoint *p, double &maxrad) override; QList components() { return m_Components.values(); } QMap &componentsWithPriorities() { return m_Components; } private: QMap m_Components; }; diff --git a/kstars/skycomponents/skymapcomposite.cpp b/kstars/skycomponents/skymapcomposite.cpp index f92cf30b7..5f7ebd49f 100644 --- a/kstars/skycomponents/skymapcomposite.cpp +++ b/kstars/skycomponents/skymapcomposite.cpp @@ -1,934 +1,934 @@ /*************************************************************************** skymapcomposite.cpp - K Desktop Planetarium ------------------- begin : 2005/07/08 copyright : (C) 2005 by Thomas Kabelmann email : thomas.kabelmann@gmx.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. * * * ***************************************************************************/ #include "skymapcomposite.h" #include "artificialhorizoncomponent.h" #include "catalogcomponent.h" #include "constellationartcomponent.h" #include "constellationboundarylines.h" #include "constellationlines.h" #include "constellationnamescomponent.h" #include "culturelist.h" #include "deepskycomponent.h" #include "deepstarcomponent.h" #include "ecliptic.h" #include "equator.h" #include "equatorialcoordinategrid.h" #include "horizoncomponent.h" #include "horizontalcoordinategrid.h" #include "ksasteroid.h" #include "kscomet.h" #ifndef KSTARS_LITE #include "kstars.h" #endif #include "kstarsdata.h" #include "milkyway.h" #include "satellitescomponent.h" #include "skylabeler.h" #include "skypainter.h" #include "solarsystemcomposite.h" #include "starcomponent.h" #include "supernovaecomponent.h" #include "syncedcatalogcomponent.h" #include "targetlistcomponent.h" #include "projections/projector.h" #include "skyobjects/deepskyobject.h" #include "skyobjects/ksplanet.h" #include "skyobjects/constellationsart.h" #ifndef KSTARS_LITE #include "flagcomponent.h" #include "ksutils.h" #include "observinglist.h" #include "skymap.h" #include "hipscomponent.h" #endif #include #include SkyMapComposite::SkyMapComposite(SkyComposite *parent) : SkyComposite(parent), m_reindexNum(J2000) { m_skyLabeler.reset(SkyLabeler::Instance()); m_skyMesh.reset(SkyMesh::Create(3)); // level 5 mesh = 8192 trixels m_skyMesh->debug(0); // 1 => print "indexing ..." // 2 => prints totals too // 10 => prints detailed lists // You can also set the debug level of individual // appendLine() and appendPoly() calls. //Add all components //Stars must come before constellation lines #ifdef KSTARS_LITE addComponent(m_MilkyWay = new MilkyWay(this), 50); addComponent(m_Stars = StarComponent::Create(this), 10); addComponent(m_EquatorialCoordinateGrid = new EquatorialCoordinateGrid(this)); addComponent(m_HorizontalCoordinateGrid = new HorizontalCoordinateGrid(this)); // Do add to components. addComponent(m_CBoundLines = new ConstellationBoundaryLines(this), 80); m_Cultures.reset(new CultureList()); addComponent(m_CLines = new ConstellationLines(this, m_Cultures.get()), 85); addComponent(m_CNames = new ConstellationNamesComponent(this, m_Cultures.get()), 90); addComponent(m_Equator = new Equator(this), 95); addComponent(m_Ecliptic = new Ecliptic(this), 95); addComponent(m_Horizon = new HorizonComponent(this), 100); addComponent(m_DeepSky = new DeepSkyComponent(this), 5); addComponent(m_ConstellationArt = new ConstellationArtComponent(this, m_Cultures.get()), 100); addComponent(m_ArtificialHorizon = new ArtificialHorizonComponent(this), 110); m_internetResolvedCat = "_Internet_Resolved"; m_manualAdditionsCat = "_Manual_Additions"; addComponent(m_internetResolvedComponent = new SyncedCatalogComponent(this, m_internetResolvedCat, true, 0), 6); addComponent(m_manualAdditionsComponent = new SyncedCatalogComponent(this, m_manualAdditionsCat, true, 0), 6); m_CustomCatalogs.reset(new SkyComposite(this)); QStringList allcatalogs = Options::showCatalogNames(); if (!allcatalogs.contains(m_internetResolvedCat)) { allcatalogs.append(m_internetResolvedCat); } if (!allcatalogs.contains(m_manualAdditionsCat)) { allcatalogs.append(m_manualAdditionsCat); } Options::setShowCatalogNames(allcatalogs); for (int i = 0; i < allcatalogs.size(); ++i) { if (allcatalogs.at(i) == m_internetResolvedCat || allcatalogs.at(i) == m_manualAdditionsCat) // This is a special catalog continue; m_CustomCatalogs->addComponent(new CatalogComponent(this, allcatalogs.at(i), false, i), 6); // FIXME: Should this be 6 or 5? See SkyMapComposite::reloadDeepSky() } addComponent(m_SolarSystem = new SolarSystemComposite(this), 2); //addComponent( m_ObservingList = new TargetListComponent( this , 0, QPen(), // &Options::obsListSymbol, &Options::obsListText ), 120 ); addComponent(m_StarHopRouteList = new TargetListComponent(this, 0, QPen()), 130); addComponent(m_Satellites = new SatellitesComponent(this), 7); addComponent(m_Supernovae = new SupernovaeComponent(this), 7); SkyMapLite::Instance()->loadingFinished(); #else addComponent(m_MilkyWay = new MilkyWay(this), 50); addComponent(m_Stars = StarComponent::Create(this), 10); addComponent(m_EquatorialCoordinateGrid = new EquatorialCoordinateGrid(this)); addComponent(m_HorizontalCoordinateGrid = new HorizontalCoordinateGrid(this)); // Do add to components. addComponent(m_CBoundLines = new ConstellationBoundaryLines(this), 80); m_Cultures.reset(new CultureList()); addComponent(m_CLines = new ConstellationLines(this, m_Cultures.get()), 85); addComponent(m_CNames = new ConstellationNamesComponent(this, m_Cultures.get()), 90); addComponent(m_Equator = new Equator(this), 95); addComponent(m_Ecliptic = new Ecliptic(this), 95); addComponent(m_Horizon = new HorizonComponent(this), 100); addComponent(m_DeepSky = new DeepSkyComponent(this), 5); addComponent(m_ConstellationArt = new ConstellationArtComponent(this, m_Cultures.get()), 100); // Hips addComponent(m_HiPS = new HIPSComponent(this)); addComponent(m_ArtificialHorizon = new ArtificialHorizonComponent(this), 110); m_internetResolvedCat = "_Internet_Resolved"; m_manualAdditionsCat = "_Manual_Additions"; addComponent(m_internetResolvedComponent = new SyncedCatalogComponent(this, m_internetResolvedCat, true, 0), 6); addComponent(m_manualAdditionsComponent = new SyncedCatalogComponent(this, m_manualAdditionsCat, true, 0), 6); m_CustomCatalogs.reset(new SkyComposite(this)); QStringList allcatalogs = Options::showCatalogNames(); for (int i = 0; i < allcatalogs.size(); ++i) { if (allcatalogs.at(i) == m_internetResolvedCat || allcatalogs.at(i) == m_manualAdditionsCat) // This is a special catalog continue; m_CustomCatalogs->addComponent(new CatalogComponent(this, allcatalogs.at(i), false, i), 6); // FIXME: Should this be 6 or 5? See SkyMapComposite::reloadDeepSky() } addComponent(m_SolarSystem = new SolarSystemComposite(this), 2); addComponent(m_Flags = new FlagComponent(this), 4); addComponent(m_ObservingList = - new TargetListComponent(this, 0, QPen(), &Options::obsListSymbol, &Options::obsListText), + new TargetListComponent(this, nullptr, QPen(), &Options::obsListSymbol, &Options::obsListText), 120); - addComponent(m_StarHopRouteList = new TargetListComponent(this, 0, QPen()), 130); + addComponent(m_StarHopRouteList = new TargetListComponent(this, nullptr, QPen()), 130); addComponent(m_Satellites = new SatellitesComponent(this), 7); addComponent(m_Supernovae = new SupernovaeComponent(this), 7); #endif connect(this, SIGNAL(progressText(QString)), KStarsData::Instance(), SIGNAL(progressText(QString))); } SkyMapComposite::~SkyMapComposite() { } void SkyMapComposite::update(KSNumbers *num) { //printf("updating SkyMapComposite\n"); //1. Milky Way //m_MilkyWay->update( data, num ); //2. Coordinate grid //m_EquatorialCoordinateGrid->update( num ); m_HorizontalCoordinateGrid->update(num); //3. Constellation boundaries //m_CBounds->update( data, num ); //4. Constellation lines //m_CLines->update( data, num ); //5. Constellation names if (m_CNames) m_CNames->update(num); //6. Equator //m_Equator->update( data, num ); //7. Ecliptic //m_Ecliptic->update( data, num ); //8. Deep sky //m_DeepSky->update( data, num ); //9. Custom catalogs m_CustomCatalogs->update(num); m_internetResolvedComponent->update(num); m_manualAdditionsComponent->update(num); //10. Stars //m_Stars->update( data, num ); //m_CLines->update( data, num ); // MUST follow stars. //12. Solar system m_SolarSystem->update(num); //13. Satellites m_Satellites->update(num); //14. Supernovae m_Supernovae->update(num); //15. Horizon m_Horizon->update(num); #ifndef KSTARS_LITE //16. Flags m_Flags->update(num); #endif } void SkyMapComposite::updateSolarSystemBodies(KSNumbers *num) { m_SolarSystem->updateSolarSystemBodies(num); } /* void SkyMapComposite::updateMoons(KSNumbers *num ) { m_SolarSystem->updateMoons( num ); } */ //Reimplement draw function so that we have control over the order of //elements, and we can add object labels // //The order in which components are drawn naturally determines the //z-ordering (the layering) of the components. Objects which //should appear "behind" others should be drawn first. void SkyMapComposite::draw(SkyPainter *skyp) { Q_UNUSED(skyp) #ifndef KSTARS_LITE SkyMap *map = SkyMap::Instance(); KStarsData *data = KStarsData::Instance(); // We delay one draw cycle before re-indexing // we MUST ensure CLines do not get re-indexed while we use DRAW_BUF // so we do it here. m_CLines->reindex(&m_reindexNum); // This queues re-indexing for the next draw cycle m_reindexNum = KSNumbers(data->updateNum()->julianDay()); // This ensures that the JIT updates are synchronized for the entire draw // cycle so the sky moves as a single sheet. May not be needed. data->syncUpdateIDs(); // prepare the aperture // FIXME_FOV: We may want to rejigger this to allow // wide-angle views --hdevalence float radius = map->projector()->fov(); if (radius > 180.0) radius = 180.0; if (m_skyMesh->inDraw()) { printf("Warning: aborting concurrent SkyMapComposite::draw()\n"); return; } m_skyMesh->inDraw(true); SkyPoint *focus = map->focus(); m_skyMesh->aperture(focus, radius + 1.0, DRAW_BUF); // divide by 2 for testing // create the no-precess aperture if needed if (Options::showEquatorialGrid() || Options::showHorizontalGrid() || Options::showCBounds() || Options::showEquator()) { m_skyMesh->index(focus, radius + 1.0, NO_PRECESS_BUF); } // clear marks from old labels and prep fonts m_skyLabeler->reset(map); m_skyLabeler->useStdFont(); // info boxes have highest label priority // FIXME: REGRESSION. Labeler now know nothing about infoboxes // map->infoBoxes()->reserveBoxes( psky ); // JM 2016-12-01: Why is this done this way?!! It's too inefficient if (KStars::Instance()) { auto &obsList = KStarsData::Instance()->observingList()->sessionList(); if (Options::obsListText()) for (auto &obj_clone : obsList) { // Find the "original" obj SkyObject *o = findByName(obj_clone->name()); // FIXME: This is sloww.... and can also fail!!! if (!o) continue; SkyLabeler::AddLabel(o, SkyLabeler::RUDE_LABEL); } } m_MilkyWay->draw(skyp); // Draw HIPS after milky way but before everything else m_HiPS->draw(skyp); m_EquatorialCoordinateGrid->draw(skyp); m_HorizontalCoordinateGrid->draw(skyp); //Draw constellation boundary lines only if we draw western constellations if (m_Cultures->current() == "Western") { m_CBoundLines->draw(skyp); m_ConstellationArt->draw(skyp); } else if (m_Cultures->current() == "Inuit") { m_ConstellationArt->draw(skyp); } m_CLines->draw(skyp); m_Equator->draw(skyp); m_Ecliptic->draw(skyp); m_DeepSky->draw(skyp); m_CustomCatalogs->draw(skyp); m_internetResolvedComponent->draw(skyp); m_manualAdditionsComponent->draw(skyp); m_Stars->draw(skyp); m_SolarSystem->drawTrails(skyp); m_SolarSystem->draw(skyp); m_Satellites->draw(skyp); m_Supernovae->draw(skyp); map->drawObjectLabels(labelObjects()); m_skyLabeler->drawQueuedLabels(); m_CNames->draw(skyp); m_Stars->drawLabels(); m_DeepSky->drawLabels(); m_ObservingList->pen = QPen(QColor(data->colorScheme()->colorNamed("ObsListColor")), 1.); if (KStars::Instance() && !m_ObservingList->list) m_ObservingList->list.reset(new SkyObjectList(KSUtils::makeVanillaPointerList( KStarsData::Instance() ->observingList() ->sessionList()))); // Make sure we never delete the pointers in m_ObservingList->list! m_ObservingList->draw(skyp); m_Flags->draw(skyp); m_StarHopRouteList->pen = QPen(QColor(data->colorScheme()->colorNamed("StarHopRouteColor")), 1.); m_StarHopRouteList->draw(skyp); m_ArtificialHorizon->draw(skyp); m_Horizon->draw(skyp); m_skyMesh->inDraw(false); // DEBUG Edit. Keywords: Trixel boundaries. Currently works only in QPainter mode // -jbb uncomment these to see trixel outlines: /* QPainter *psky = dynamic_cast< QPainter *>( skyp ); if( psky ) { qCDebug(KSTARS) << "Drawing trixel boundaries for debugging."; psky->setPen( QPen( QBrush( QColor( "yellow" ) ), 1, Qt::SolidLine ) ); m_skyMesh->draw( *psky, OBJ_NEAREST_BUF ); SkyMesh *p; if( p = SkyMesh::Instance( 6 ) ) { qCDebug(KSTARS) << "We have a deep sky mesh to draw"; p->draw( *psky, OBJ_NEAREST_BUF ); } psky->setPen( QPen( QBrush( QColor( "green" ) ), 1, Qt::SolidLine ) ); m_skyMesh->draw( *psky, NO_PRECESS_BUF ); if( p ) p->draw( *psky, NO_PRECESS_BUF ); } */ #endif } //Select nearest object to the given skypoint, but give preference //to certain object types. //we multiply each object type's smallest angular distance by the //following factors before selecting the final nearest object: // faint stars = 1.0 (not weighted) // stars brighter than 4th mag = 0.75 // IC catalog = 0.8 // NGC catalog = 0.6 // "other" catalog = 0.6 // Messier object = 0.5 // custom object = 0.5 // Solar system = 0.25 SkyObject *SkyMapComposite::objectNearest(SkyPoint *p, double &maxrad) { double rTry = maxrad; double rBest = maxrad; - SkyObject *oTry = 0; - SkyObject *oBest = 0; + SkyObject *oTry = nullptr; + SkyObject *oBest = nullptr; //printf("%.1f %.1f\n", p->ra().Degrees(), p->dec().Degrees() ); m_skyMesh->aperture(p, maxrad + 1.0, OBJ_NEAREST_BUF); oBest = m_Stars->objectNearest(p, rBest); //reduce rBest by 0.75 for stars brighter than 4th mag if (oBest && oBest->mag() < 4.0) rBest *= 0.75; else if (oBest && oBest->mag() > 12.0) rBest *= 1.25; // TODO: Add support for deep star catalogs //m_DeepSky internally discriminates among deepsky catalogs //and renormalizes rTry oTry = m_DeepSky->objectNearest(p, rTry); if (rTry < rBest) { rBest = rTry; oBest = oTry; } for (int i = 0; i < m_DeepStars.size(); ++i) { rTry = maxrad; oTry = m_DeepStars.at(i)->objectNearest(p, rTry); if (rTry < rBest) { rBest = rTry; oBest = oTry; } } rTry = maxrad; oTry = m_CustomCatalogs->objectNearest(p, rTry); rTry *= 0.5; if (rTry < rBest) { rBest = rTry; oBest = oTry; } rTry = maxrad; oTry = m_internetResolvedComponent->objectNearest(p, rTry); rTry *= 0.5; if (rTry < rBest) { rBest = rTry; oBest = oTry; } rTry = maxrad; oTry = m_manualAdditionsComponent->objectNearest(p, rTry); rTry *= 0.5; if (rTry < rBest) { rBest = rTry; oBest = oTry; } rTry = maxrad; oTry = m_SolarSystem->objectNearest(p, rTry); if (!dynamic_cast(oTry) && !dynamic_cast( oTry)) // There are gazillions of faint asteroids and comets; we want to prevent them from getting precedence { rTry *= 0.25; // this is either sun, moon, or one of the major planets or their moons. } else { if (std::isfinite(oTry->mag()) && oTry->mag() < 12.0) { rTry *= 0.75; // Bright comets / asteroids get some precedence. } } if (rTry < rBest) { rBest = rTry; oBest = oTry; } rTry = maxrad; oTry = m_Satellites->objectNearest(p, rTry); if (rTry < rBest) { rBest = rTry; oBest = oTry; } rTry = maxrad; oTry = m_Supernovae->objectNearest(p, rTry); //qCDebug(KSTARS)<name2(); maxrad = rBest; return oBest; //will be 0 if no object nearer than maxrad was found } SkyObject *SkyMapComposite::starNearest(SkyPoint *p, double &maxrad) { double rtry = maxrad; - SkyObject *star = 0; + SkyObject *star = nullptr; m_skyMesh->aperture(p, maxrad + 1.0, OBJ_NEAREST_BUF); star = m_Stars->objectNearest(p, rtry); //reduce rBest by 0.75 for stars brighter than 4th mag if (star && star->mag() < 4.0) rtry *= 0.75; // TODO: Add Deep Star Catalog support maxrad = rtry; return star; } bool SkyMapComposite::addNameLabel(SkyObject *o) { if (!o) return false; labelObjects().append(o); return true; } bool SkyMapComposite::removeNameLabel(SkyObject *o) { if (!o) return false; int index = labelObjects().indexOf(o); if (index < 0) return false; labelObjects().removeAt(index); return true; } QHash &SkyMapComposite::getObjectNames() { return m_ObjectNames; } QHash>> &SkyMapComposite::getObjectLists() { return m_ObjectLists; } QList SkyMapComposite::findObjectsInArea(const SkyPoint &p1, const SkyPoint &p2) { const SkyRegion ®ion = m_skyMesh->skyRegion(p1, p2); QList list; // call objectsInArea( QList&, const SkyRegion& ) for each of the // components of the SkyMapComposite if (m_Stars->selected()) m_Stars->objectsInArea(list, region); if (m_DeepSky->selected()) m_DeepSky->objectsInArea(list, region); return list; } SkyObject *SkyMapComposite::findByName(const QString &name) { #ifndef KSTARS_LITE if (KStars::Closing) return nullptr; #endif //We search the children in an "intelligent" order (most-used //object types first), in order to avoid wasting too much time //looking for a match. The most important part of this ordering //is that stars should be last (because the stars list is so long) - SkyObject *o = 0; + SkyObject *o = nullptr; o = m_SolarSystem->findByName(name); if (o) return o; o = m_DeepSky->findByName(name); if (o) return o; o = m_CustomCatalogs->findByName(name); if (o) return o; o = m_internetResolvedComponent->findByName(name); if (o) return o; o = m_manualAdditionsComponent->findByName(name); if (o) return o; o = m_CNames->findByName(name); if (o) return o; o = m_Stars->findByName(name); if (o) return o; o = m_Supernovae->findByName(name); if (o) return o; o = m_Satellites->findByName(name); if (o) return o; - return 0; + return nullptr; } SkyObject *SkyMapComposite::findStarByGenetiveName(const QString name) { return m_Stars->findStarByGenetiveName(name); } KSPlanetBase *SkyMapComposite::planet(int n) { if (n == KSPlanetBase::SUN) return (KSPlanetBase *)(m_SolarSystem->findByName("Sun")); if (n == KSPlanetBase::MERCURY) return (KSPlanetBase *)(m_SolarSystem->findByName(i18n("Mercury"))); if (n == KSPlanetBase::VENUS) return (KSPlanetBase *)(m_SolarSystem->findByName(i18n("Venus"))); if (n == KSPlanetBase::MOON) return (KSPlanetBase *)(m_SolarSystem->findByName("Moon")); if (n == KSPlanetBase::MARS) return (KSPlanetBase *)(m_SolarSystem->findByName(i18n("Mars"))); if (n == KSPlanetBase::JUPITER) return (KSPlanetBase *)(m_SolarSystem->findByName(i18n("Jupiter"))); if (n == KSPlanetBase::SATURN) return (KSPlanetBase *)(m_SolarSystem->findByName(i18n("Saturn"))); if (n == KSPlanetBase::URANUS) return (KSPlanetBase *)(m_SolarSystem->findByName(i18n("Uranus"))); if (n == KSPlanetBase::NEPTUNE) return (KSPlanetBase *)(m_SolarSystem->findByName(i18n("Neptune"))); //if ( n == KSPlanetBase::PLUTO ) return (KSPlanetBase*)(m_SolarSystem->findByName( i18n( "Pluto" ) ) ); - return 0; + return nullptr; } void SkyMapComposite::addCustomCatalog(const QString &filename, int index) { CatalogComponent *cc = new CatalogComponent(this, filename, false, index); if (cc->objectList().size()) { m_CustomCatalogs->addComponent(cc); } else { delete cc; } } void SkyMapComposite::removeCustomCatalog(const QString &name) { foreach (SkyComponent *sc, m_CustomCatalogs->components()) { CatalogComponent *ccc = (CatalogComponent *)sc; if (ccc->name() == name) { m_CustomCatalogs->removeComponent(ccc); return; } } qWarning() << i18n("Could not find custom catalog component named %1.", name); } void SkyMapComposite::reloadCLines() { #ifndef KSTARS_LITE Q_ASSERT(!SkyMapDrawAbstract::drawLock()); SkyMapDrawAbstract::setDrawLock( true); // This is not (yet) multithreaded, so I think we don't have to worry about overwriting the state of an existing lock --asimha removeComponent(m_CLines); delete m_CLines; addComponent(m_CLines = new ConstellationLines(this, m_Cultures.get())); SkyMapDrawAbstract::setDrawLock(false); #endif } void SkyMapComposite::reloadCNames() { // Q_ASSERT( !SkyMapDrawAbstract::drawLock() ); // SkyMapDrawAbstract::setDrawLock( true ); // This is not (yet) multithreaded, so I think we don't have to worry about overwriting the state of an existing lock --asimha // objectNames(SkyObject::CONSTELLATION).clear(); // delete m_CNames; // m_CNames = 0; // m_CNames = new ConstellationNamesComponent( this, m_Cultures.get() ); // SkyMapDrawAbstract::setDrawLock( false ); objectNames(SkyObject::CONSTELLATION).clear(); removeComponent(m_CNames); delete m_CNames; addComponent(m_CNames = new ConstellationNamesComponent(this, m_Cultures.get())); } void SkyMapComposite::reloadConstellationArt() { #ifndef KSTARS_LITE Q_ASSERT(!SkyMapDrawAbstract::drawLock()); SkyMapDrawAbstract::setDrawLock(true); removeComponent(m_ConstellationArt); delete m_ConstellationArt; addComponent(m_ConstellationArt = new ConstellationArtComponent(this, m_Cultures.get())); SkyMapDrawAbstract::setDrawLock(false); #endif } void SkyMapComposite::reloadDeepSky() { #ifndef KSTARS_LITE Q_ASSERT(!SkyMapDrawAbstract::drawLock()); // Deselect object if selected! If not deselected then InfoBox tries to // get the name of an object which may not exist (getLongName) // FIXME (spacetime): Is there a better way? // Current Solution: Look for the nearest star in the region and select it. SkyMap *current_map = KStars::Instance()->map(); double maxrad = 30.0; SkyPoint center_point = current_map->getCenterPoint(); current_map->setClickedObject(KStars::Instance()->data()->skyComposite()->starNearest(¢er_point, maxrad)); current_map->setClickedPoint(current_map->clickedObject()); current_map->slotCenter(); // Remove and Regenerate set of catalog objects // // FIXME: Why should we do this? Because it messes up observing // list really bad to delete and regenerate SkyObjects. SkyMapDrawAbstract::setDrawLock(true); m_CustomCatalogs.reset(new SkyComposite(this)); removeComponent(m_internetResolvedComponent); delete m_internetResolvedComponent; addComponent(m_internetResolvedComponent = new SyncedCatalogComponent(this, m_internetResolvedCat, true, 0), 6); removeComponent(m_manualAdditionsComponent); delete m_manualAdditionsComponent; addComponent(m_manualAdditionsComponent = new SyncedCatalogComponent(this, m_manualAdditionsCat, true, 0), 6); QStringList allcatalogs = Options::showCatalogNames(); for (int i = 0; i < allcatalogs.size(); ++i) { if (allcatalogs.at(i) == m_internetResolvedCat || allcatalogs.at(i) == m_manualAdditionsCat) // These are special catalogs continue; m_CustomCatalogs->addComponent(new CatalogComponent(this, allcatalogs.at(i), false, i), 5); // FIXME: Should this be 6 or 5? See SkyMapComposite::SkyMapComposite() } // FIXME: We should also reload anything that refers to SkyObject // * in memory, because all the old SkyObjects are now gone! This // includes the observing list. Otherwise, expect a bad, bad crash // that is hard to debug! -- AS SkyMapDrawAbstract::setDrawLock(false); #endif } bool SkyMapComposite::isLocalCNames() { return m_CNames->isLocalCNames(); } void SkyMapComposite::emitProgressText(const QString &message) { emit progressText(message); #ifndef Q_OS_ANDROID //Can cause crashes on Android, investigate it qApp->processEvents(); // -jbb: this seemed to make it work. #endif //qCDebug(KSTARS) << QString("PROGRESS TEXT: %1\n").arg( message ); } const QList &SkyMapComposite::deepSkyObjects() const { return m_DeepSky->objectList(); } const QList &SkyMapComposite::constellationNames() const { return m_CNames->objectList(); } // Returns only named stars, and should not be used const QList &SkyMapComposite::stars() const { return m_Stars->objectList(); } const QList &SkyMapComposite::asteroids() const { return m_SolarSystem->asteroids(); } const QList &SkyMapComposite::comets() const { return m_SolarSystem->comets(); } const QList &SkyMapComposite::supernovae() const { return m_Supernovae->objectList(); } QList SkyMapComposite::planets() { return solarSystemComposite()->planetObjects(); } //Store it permanently /* QList SkyMapComposite::moons() { QList skyObjects; foreach(PlanetMoonsComponent *pMoons, m_SolarSystem->planetMoonsComponent()) { PlanetMoons *moons = pMoons->getMoons(); for(int i = 0; i < moons->nMoons(); ++i) { skyObjects.append(moons->moon(i)); } } return skyObjects; } */ const QList *SkyMapComposite::getSkyObjectsList(SkyObject::TYPE t) { switch (t) { case SkyObject::STAR: return &m_Stars->objectList(); break; case SkyObject::CATALOG_STAR: return nullptr; break; case SkyObject::PLANET: return &m_SolarSystem->planetObjects(); break; case SkyObject::COMET: return &comets(); break; case SkyObject::ASTEROID: return &asteroids(); break; case SkyObject::MOON: return &m_SolarSystem->moons(); break; case SkyObject::GALAXY: case SkyObject::PLANETARY_NEBULA: case SkyObject::GASEOUS_NEBULA: case SkyObject::GLOBULAR_CLUSTER: case SkyObject::OPEN_CLUSTER: return nullptr; break; case SkyObject::CONSTELLATION: return &constellationNames(); break; case SkyObject::SUPERNOVA: return &supernovae(); break; default: return nullptr; } //return nullptr; } KSPlanet *SkyMapComposite::earth() { return m_SolarSystem->earth(); } QList SkyMapComposite::customCatalogs() { return m_CustomCatalogs->components(); } QStringList SkyMapComposite::getCultureNames() { return m_Cultures->getNames(); } QString SkyMapComposite::getCultureName(int index) { return m_Cultures->getName(index); } void SkyMapComposite::setCurrentCulture(QString culture) { m_Cultures->setCurrent(culture); } QString SkyMapComposite::currentCulture() { return m_Cultures->current(); } #ifndef KSTARS_LITE FlagComponent *SkyMapComposite::flags() { return m_Flags; } #endif SatellitesComponent *SkyMapComposite::satellites() { return m_Satellites; } SupernovaeComponent *SkyMapComposite::supernovaeComponent() { return m_Supernovae; } ArtificialHorizonComponent *SkyMapComposite::artificialHorizon() { return m_ArtificialHorizon; } diff --git a/kstars/skycomponents/skymapcomposite.h b/kstars/skycomponents/skymapcomposite.h index 9de4df102..6a3347881 100644 --- a/kstars/skycomponents/skymapcomposite.h +++ b/kstars/skycomponents/skymapcomposite.h @@ -1,280 +1,280 @@ /*************************************************************************** skymapcomposite.h - K Desktop Planetarium ------------------- begin : 2005/07/08 copyright : (C) 2005 by Thomas Kabelmann email : thomas.kabelmann@gmx.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 "skycomposite.h" #include "ksnumbers.h" #include "skyobject.h" #include #include class QPolygonF; class ArtificialHorizonComponent; class ConstellationArtComponent; class ConstellationBoundaryLines; class ConstellationLines; class ConstellationNamesComponent; class ConstellationsArt; class CultureList; class DeepSkyComponent; class DeepSkyObject; class DeepStarComponent; class Ecliptic; class Equator; class EquatorialCoordinateGrid; class FlagComponent; class HorizontalCoordinateGrid; class HorizonComponent; class KSPlanet; class KSPlanetBase; class MilkyWay; class SatellitesComponent; class SkyLabeler; class SkyMap; class SkyMesh; class SkyObject; class SolarSystemComposite; class StarComponent; class SupernovaeComponent; class SyncedCatalogComponent; class TargetListComponent; class HIPSComponent; /** * @class SkyMapComposite * SkyMapComposite is the root object in the object hierarchy of the sky map. * All requests to update, init, draw etc. will be done with this class. * The requests will be delegated to it's children. * The object hierarchy will created by adding new objects via addComponent(). * * @author Thomas Kabelmann * @version 0.1 */ class SkyMapComposite : public QObject, public SkyComposite { Q_OBJECT public: /** * Constructor * @p parent pointer to the parent SkyComponent */ explicit SkyMapComposite(SkyComposite *parent = nullptr); ~SkyMapComposite() override; - void update(KSNumbers *num = 0) override; + void update(KSNumbers *num = nullptr) override; /** * @short Delegate planet position updates to the SolarSystemComposite * * Planet positions change over time, so they need to be recomputed * periodically, but not on every call to update(). This function * will recompute the positions of all solar system bodies except the * Earth's Moon, Jupiter's Moons AND Saturn Moons (because these objects' positions * change on a much more rapid timescale). * @p num Pointer to the KSNumbers object * @sa update() * @sa updateMoons() * @sa SolarSystemComposite::updatePlanets() */ void updateSolarSystemBodies(KSNumbers *num) override; /** * @short Delegate moon position updates to the SolarSystemComposite * * Planet positions change over time, so they need to be recomputed * periodically, but not on every call to update(). This function * will recompute the positions of the Earth's Moon and Jupiter's four * Galilean moons. These objects are done separately from the other * solar system bodies, because their positions change more rapidly, * and so updateMoons() must be called more often than updatePlanets(). * @p num Pointer to the KSNumbers object * @sa update() * @sa updatePlanets() * @sa SolarSystemComposite::updateMoons() */ // virtual void updateMoons( KSNumbers *num ); /** * @short Delegate draw requests to all sub components * @p psky Reference to the QPainter on which to paint */ void draw(SkyPainter *skyp) override; /** * @return the object nearest a given point in the sky. * @param p The point to find an object near * @param maxrad The maximum search radius, in Degrees * @note the angular separation to the matched object is returned * through the maxrad variable. */ SkyObject *objectNearest(SkyPoint *p, double &maxrad) override; /** * @return the star nearest a given point in the sky. * @param p The point to find a star near * @param maxrad The maximum search radius, in Degrees * @note the angular separation to the matched star is returned * through the maxrad variable. */ SkyObject *starNearest(SkyPoint *p, double &maxrad); /** * @short Search the children of this SkyMapComposite for * a SkyObject whose name matches the argument. * * The objects' primary, secondary and long-form names will * all be checked for a match. * @note Overloaded from SkyComposite. In this version, we search * the most likely object classes first to be more efficient. * @p name the name to be matched * @return a pointer to the SkyObject whose name matches * the argument, or a nullptr pointer if no match was found. */ SkyObject *findByName(const QString &name) override; /** * @return the list of objects in the region defined by skypoints * @param p1 first sky point (top-left vertex of rectangular region) * @param p2 second sky point (bottom-right vertex of rectangular region) */ QList findObjectsInArea(const SkyPoint &p1, const SkyPoint &p2); void addCustomCatalog(const QString &filename, int index); void removeCustomCatalog(const QString &name); bool addNameLabel(SkyObject *o); bool removeNameLabel(SkyObject *o); void reloadDeepSky(); void reloadAsteroids(); void reloadComets(); void reloadCLines(); void reloadCNames(); void reloadConstellationArt(); #ifndef KSTARS_LITE FlagComponent *flags(); #endif SatellitesComponent *satellites(); SupernovaeComponent *supernovaeComponent(); ArtificialHorizonComponent *artificialHorizon(); /** Getters for SkyComponents **/ inline HorizonComponent *horizon() { return m_Horizon; } inline ConstellationBoundaryLines *constellationBoundary() { return m_CBoundLines; } inline ConstellationLines *constellationLines() { return m_CLines; } inline Ecliptic *ecliptic() { return m_Ecliptic; } inline Equator *equator() { return m_Equator; } inline EquatorialCoordinateGrid *equatorialCoordGrid() { return m_EquatorialCoordinateGrid; } inline HorizontalCoordinateGrid *horizontalCoordGrid() { return m_HorizontalCoordinateGrid; } inline ConstellationArtComponent *constellationArt() { return m_ConstellationArt; } inline SolarSystemComposite *solarSystemComposite() { return m_SolarSystem; } inline ConstellationNamesComponent *constellationNamesComponent() { return m_CNames; } inline DeepSkyComponent *deepSkyComponent() { return m_DeepSky; } inline MilkyWay *milkyWay() { return m_MilkyWay; } inline SyncedCatalogComponent *internetResolvedComponent() { return m_internetResolvedComponent; } inline SyncedCatalogComponent *manualAdditionsComponent() { return m_manualAdditionsComponent; } //Accessors for StarComponent SkyObject *findStarByGenetiveName(const QString name); void emitProgressText(const QString &message) override; QList &labelObjects() { return m_LabeledObjects; } const QList &deepSkyObjects() const; const QList &constellationNames() const; const QList &stars() const; const QList &asteroids() const; const QList &comets() const; const QList &supernovae() const; QList planets(); // QList moons(); const QList *getSkyObjectsList(SkyObject::TYPE t); KSPlanet *earth(); KSPlanetBase *planet(int n); QStringList getCultureNames(); QString getCultureName(int index); QString currentCulture(); void setCurrentCulture(QString culture); bool isLocalCNames(); QList customCatalogs(); inline TargetListComponent *getStarHopRouteList() { return m_StarHopRouteList; } signals: void progressText(const QString &message); private: QHash &getObjectNames() override; QHash>> &getObjectLists() override; std::unique_ptr m_Cultures; ConstellationBoundaryLines *m_CBoundLines { nullptr }; ConstellationNamesComponent *m_CNames { nullptr }; ConstellationLines *m_CLines { nullptr }; ConstellationArtComponent *m_ConstellationArt { nullptr }; EquatorialCoordinateGrid *m_EquatorialCoordinateGrid { nullptr }; HorizontalCoordinateGrid *m_HorizontalCoordinateGrid { nullptr }; DeepSkyComponent *m_DeepSky { nullptr }; Equator *m_Equator { nullptr }; ArtificialHorizonComponent *m_ArtificialHorizon { nullptr }; Ecliptic *m_Ecliptic { nullptr }; HorizonComponent *m_Horizon { nullptr }; MilkyWay *m_MilkyWay { nullptr }; SolarSystemComposite *m_SolarSystem { nullptr }; std::unique_ptr m_CustomCatalogs; StarComponent *m_Stars { nullptr }; #ifndef KSTARS_LITE FlagComponent *m_Flags { nullptr }; HIPSComponent *m_HiPS { nullptr }; #endif TargetListComponent *m_ObservingList { nullptr }; TargetListComponent *m_StarHopRouteList { nullptr }; SatellitesComponent *m_Satellites { nullptr }; SupernovaeComponent *m_Supernovae { nullptr }; SyncedCatalogComponent *m_internetResolvedComponent { nullptr }; SyncedCatalogComponent *m_manualAdditionsComponent { nullptr }; std::unique_ptr m_skyMesh; std::unique_ptr m_skyLabeler; KSNumbers m_reindexNum; QList m_DeepStars; QList m_LabeledObjects; QHash m_ObjectNames; QHash>> m_ObjectLists; QHash m_ConstellationNames; QString m_internetResolvedCat; // Holds the name of the internet resolved catalog QString m_manualAdditionsCat; }; diff --git a/kstars/skycomponents/solarsystemcomposite.cpp b/kstars/skycomponents/solarsystemcomposite.cpp index 25c3b7381..08563635f 100644 --- a/kstars/skycomponents/solarsystemcomposite.cpp +++ b/kstars/skycomponents/solarsystemcomposite.cpp @@ -1,197 +1,197 @@ /*************************************************************************** solarsystemcomposite.h - K Desktop Planetarium ------------------- begin : 2005/01/09 copyright : (C) 2005 by Thomas Kabelmann email : thomas.kabelmann@gmx.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. * * * ***************************************************************************/ #include "solarsystemcomposite.h" #include "asteroidscomponent.h" #include "cometscomponent.h" #include "kstarsdata.h" #include "Options.h" #ifndef KSTARS_LITE #include "skymap.h" #endif #include "solarsystemsinglecomponent.h" #include "skyobjects/ksmoon.h" #include "skyobjects/ksplanet.h" #include "skyobjects/kssun.h" SolarSystemComposite::SolarSystemComposite(SkyComposite *parent) : SkyComposite(parent) { emitProgressText(i18n("Loading solar system")); m_Earth = new KSPlanet(I18N_NOOP("Earth"), QString(), QColor("white"), 12756.28 /*diameter in km*/); m_Sun = new KSSun(); SolarSystemSingleComponent *sun = new SolarSystemSingleComponent(this, m_Sun, Options::showSun); addComponent(sun, 2); m_Moon = new KSMoon(); SolarSystemSingleComponent *moon = new SolarSystemSingleComponent(this, m_Moon, Options::showMoon); addComponent(moon, 3); SolarSystemSingleComponent *mercury = new SolarSystemSingleComponent(this, new KSPlanet(KSPlanetBase::MERCURY), Options::showMercury); addComponent(mercury, 4); SolarSystemSingleComponent *venus = new SolarSystemSingleComponent(this, new KSPlanet(KSPlanetBase::VENUS), Options::showVenus); addComponent(venus, 4); SolarSystemSingleComponent *mars = new SolarSystemSingleComponent(this, new KSPlanet(KSPlanetBase::MARS), Options::showMars); addComponent(mars, 4); SolarSystemSingleComponent *jup = new SolarSystemSingleComponent(this, new KSPlanet(KSPlanetBase::JUPITER), Options::showJupiter); addComponent(jup, 4); /* m_JupiterMoons = new PlanetMoonsComponent( this, jup, KSPlanetBase::JUPITER); addComponent( m_JupiterMoons, 5 ); */ SolarSystemSingleComponent *sat = new SolarSystemSingleComponent(this, new KSPlanet(KSPlanetBase::SATURN), Options::showSaturn); addComponent(sat, 4); SolarSystemSingleComponent *uranus = new SolarSystemSingleComponent(this, new KSPlanet(KSPlanetBase::URANUS), Options::showUranus); addComponent(uranus, 4); SolarSystemSingleComponent *nep = new SolarSystemSingleComponent(this, new KSPlanet(KSPlanetBase::NEPTUNE), Options::showNeptune); addComponent(nep, 4); //addComponent( new SolarSystemSingleComponent( this, new KSPluto(), Options::showPluto ) ); m_planets.append(sun); m_planets.append(moon); m_planets.append(mercury); m_planets.append(venus); m_planets.append(mars); m_planets.append(sat); m_planets.append(jup); m_planets.append(uranus); m_planets.append(nep); /*m_planetObjects.append(sun->planet()); m_planetObjects.append(moon->planet()); m_planetObjects.append(mercury->planet()); m_planetObjects.append(venus->planet()); m_planetObjects.append(mars->planet()); m_planetObjects.append(sat->planet()); m_planetObjects.append(jup->planet()); m_planetObjects.append(uranus->planet()); m_planetObjects.append(nep->planet()); foreach(PlanetMoonsComponent *pMoons, planetMoonsComponent()) { PlanetMoons *moons = pMoons->getMoons(); for(int i = 0; i < moons->nMoons(); ++i) { SkyObject *moon = moons->moon(i); objectLists(SkyObject::MOON).append(QPair(moon->name(), moon)); } }*/ addComponent(m_AsteroidsComponent = new AsteroidsComponent(this), 7); addComponent(m_CometsComponent = new CometsComponent(this), 7); } SolarSystemComposite::~SolarSystemComposite() { delete m_Earth; } bool SolarSystemComposite::selected() { #ifndef KSTARS_LITE return Options::showSolarSystem() && !(Options::hideOnSlew() && Options::hidePlanets() && SkyMap::IsSlewing()); #else return Options::showSolarSystem() && !(Options::hideOnSlew() && Options::hidePlanets()); #endif } void SolarSystemComposite::update(KSNumbers *num) { // if ( ! selected() ) return; KStarsData *data = KStarsData::Instance(); m_Sun->EquatorialToHorizontal(data->lst(), data->geo()->lat()); m_Moon->EquatorialToHorizontal(data->lst(), data->geo()->lat()); // m_JupiterMoons->update( num ); foreach (SkyComponent *comp, components()) { comp->update(num); } } void SolarSystemComposite::updateSolarSystemBodies(KSNumbers *num) { m_Earth->findPosition(num); foreach (SkyComponent *comp, components()) { comp->updateSolarSystemBodies(num); } } void SolarSystemComposite::updateMoons(KSNumbers *num) { // if ( ! selected() ) return; KStarsData *data = KStarsData::Instance(); m_Sun->findPosition(num); m_Moon->findPosition(num, data->geo()->lat(), data->lst()); - m_Moon->findPhase(0); + m_Moon->findPhase(nullptr); // m_JupiterMoons->updateMoons( num ); } void SolarSystemComposite::drawTrails(SkyPainter *skyp) { if (selected()) foreach (SkyComponent *comp, components()) comp->drawTrails(skyp); } const QList &SolarSystemComposite::asteroids() const { return m_AsteroidsComponent->objectList(); } const QList &SolarSystemComposite::comets() const { return m_CometsComponent->objectList(); } const QList &SolarSystemComposite::planetObjects() const { return m_planetObjects; } const QList &SolarSystemComposite::moons() const { return m_moons; } CometsComponent *SolarSystemComposite::cometsComponent() { return m_CometsComponent; } AsteroidsComponent *SolarSystemComposite::asteroidsComponent() { return m_AsteroidsComponent; } const QList &SolarSystemComposite::planets() const { return m_planets; } /* QList SolarSystemComposite::planetMoonsComponent() const { return QList ({m_JupiterMoons}); } */ diff --git a/kstars/skycomponents/solarsystemsinglecomponent.cpp b/kstars/skycomponents/solarsystemsinglecomponent.cpp index 1e2a2e3a1..fbdc8c4fb 100644 --- a/kstars/skycomponents/solarsystemsinglecomponent.cpp +++ b/kstars/skycomponents/solarsystemsinglecomponent.cpp @@ -1,124 +1,124 @@ /*************************************************************************** solarsystemsinglecomponent.cpp - K Desktop Planetarium ------------------- begin : 2005/30/08 copyright : (C) 2005 by Thomas Kabelmann email : thomas.kabelmann@gmx.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. * * * ***************************************************************************/ #include "solarsystemsinglecomponent.h" #include "solarsystemcomposite.h" #include "skycomponent.h" #include "dms.h" #include "kstarsdata.h" #include "skyobjects/starobject.h" #include "skyobjects/ksplanetbase.h" #include "skyobjects/ksplanet.h" #ifdef KSTARS_LITE #include "skymaplite.h" #else #include "skymap.h" #endif #include "Options.h" #include "skylabeler.h" #include "skypainter.h" #include "projections/projector.h" SolarSystemSingleComponent::SolarSystemSingleComponent(SolarSystemComposite *parent, KSPlanetBase *kspb, bool (*visibleMethod)()) : SkyComponent(parent), visible(visibleMethod), m_Earth(parent->earth()), m_Planet(kspb) { m_Planet->loadData(); if (!m_Planet->name().isEmpty()) { objectNames(m_Planet->type()).append(m_Planet->name()); objectLists(m_Planet->type()).append(QPair(m_Planet->name(), m_Planet)); } if (!m_Planet->longname().isEmpty() && m_Planet->longname() != m_Planet->name()) { objectNames(m_Planet->type()).append(m_Planet->longname()); objectLists(m_Planet->type()).append(QPair(m_Planet->longname(), m_Planet)); } } SolarSystemSingleComponent::~SolarSystemSingleComponent() { removeFromNames(m_Planet); removeFromLists(m_Planet); delete m_Planet; } bool SolarSystemSingleComponent::selected() { return visible(); } SkyObject *SolarSystemSingleComponent::findByName(const QString &name) { if (QString::compare(m_Planet->name(), name, Qt::CaseInsensitive) == 0 || QString::compare(m_Planet->longname(), name, Qt::CaseInsensitive) == 0 || QString::compare(m_Planet->name2(), name, Qt::CaseInsensitive) == 0) return m_Planet; - return 0; + return nullptr; } SkyObject *SolarSystemSingleComponent::objectNearest(SkyPoint *p, double &maxrad) { double r = m_Planet->angularDistanceTo(p).Degrees(); if (r < maxrad) { maxrad = r; return m_Planet; } - return 0; + return nullptr; } void SolarSystemSingleComponent::update(KSNumbers *) { KStarsData *data = KStarsData::Instance(); if (selected()) m_Planet->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } void SolarSystemSingleComponent::updateSolarSystemBodies(KSNumbers *num) { if (selected()) { KStarsData *data = KStarsData::Instance(); m_Planet->findPosition(num, data->geo()->lat(), data->lst(), m_Earth); m_Planet->EquatorialToHorizontal(data->lst(), data->geo()->lat()); if (m_Planet->hasTrail()) m_Planet->updateTrail(data->lst(), data->geo()->lat()); } } void SolarSystemSingleComponent::draw(SkyPainter *skyp) { if (!selected()) return; skyp->setPen(m_Planet->color()); skyp->setBrush(m_Planet->color()); bool drawn = skyp->drawPlanet(m_Planet); if (drawn && Options::showPlanetNames()) SkyLabeler::AddLabel(m_Planet, SkyLabeler::PLANET_LABEL); } void SolarSystemSingleComponent::drawTrails(SkyPainter *skyp) { if (selected()) m_Planet->drawTrail(skyp); } diff --git a/kstars/skycomponents/starblock.cpp b/kstars/skycomponents/starblock.cpp index 88a89d7c0..5e82ffba4 100644 --- a/kstars/skycomponents/starblock.cpp +++ b/kstars/skycomponents/starblock.cpp @@ -1,127 +1,127 @@ /*************************************************************************** starblock.cpp - K Desktop Planetarium ------------------- begin : 9 Jun 2008 copyright : (C) 2008 by Akarsh Simha email : akarshsimha@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. * * * ***************************************************************************/ #include #include "starblock.h" #include "skyobjects/starobject.h" #include "starcomponent.h" #include "skyobjects/stardata.h" #include "skyobjects/deepstardata.h" #ifdef KSTARS_LITE #include "skymaplite.h" #include "kstarslite/skyitems/skynodes/pointsourcenode.h" StarNode::StarNode() : starNode(0) { } StarNode::~StarNode() { if (starNode) { SkyMapLite::Instance()->deleteSkyNode(starNode); qDebug() << "REAL NODE DESTRUCTOR"; } } #endif StarBlock::StarBlock(int nstars) - : faintMag(-5), brightMag(35), parent(0), prev(0), next(0), drawID(0), nStars(0), + : faintMag(-5), brightMag(35), parent(nullptr), prev(nullptr), next(nullptr), drawID(0), nStars(0), #ifdef KSTARS_LITE stars(nstars, StarNode()) #else stars(nstars, StarObject()) #endif { } void StarBlock::reset() { if (parent) parent->releaseBlock(this); parent = nullptr; faintMag = -5.0; brightMag = 35.0; nStars = 0; } StarBlock::~StarBlock() { } #ifdef KSTARS_LITE StarNode *StarBlock::addStar(const StarData &data) { if (isFull()) return 0; StarNode &node = stars[nStars++]; StarObject &star = node.star; star.init(&data); if (star.mag() > faintMag) faintMag = star.mag(); if (star.mag() < brightMag) brightMag = star.mag(); return &node; } StarNode *StarBlock::addStar(const DeepStarData &data) { if (isFull()) return 0; StarNode &node = stars[nStars++]; StarObject &star = node.star; star.init(&data); if (star.mag() > faintMag) faintMag = star.mag(); if (star.mag() < brightMag) brightMag = star.mag(); return &node; } #else StarObject *StarBlock::addStar(const StarData &data) { if (isFull()) - return 0; + return nullptr; StarObject &star = stars[nStars++]; star.init(&data); if (star.mag() > faintMag) faintMag = star.mag(); if (star.mag() < brightMag) brightMag = star.mag(); return ☆ } StarObject *StarBlock::addStar(const DeepStarData &data) { if (isFull()) - return 0; + return nullptr; StarObject &star = stars[nStars++]; star.init(&data); if (star.mag() > faintMag) faintMag = star.mag(); if (star.mag() < brightMag) brightMag = star.mag(); return ☆ } #endif diff --git a/kstars/skycomponents/starblockfactory.cpp b/kstars/skycomponents/starblockfactory.cpp index 6a8bbe016..aea5f777d 100644 --- a/kstars/skycomponents/starblockfactory.cpp +++ b/kstars/skycomponents/starblockfactory.cpp @@ -1,319 +1,319 @@ /*************************************************************************** starblockfactory.cpp - K Desktop Planetarium ------------------- begin : 7 Jun 2008 copyright : (C) 2008 by Akarsh Simha email : akarshsimha@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. * * * ***************************************************************************/ #include "starblockfactory.h" #include "starblock.h" #include "starobject.h" #include // TODO: Implement a better way of deciding this #define DEFAULT_NCACHE 12 -StarBlockFactory *StarBlockFactory::pInstance = 0; +StarBlockFactory *StarBlockFactory::pInstance = nullptr; StarBlockFactory *StarBlockFactory::Instance() { if (!pInstance) pInstance = new StarBlockFactory(); return pInstance; } StarBlockFactory::StarBlockFactory() { first = nullptr; last = nullptr; nBlocks = 0; drawID = 0; nCache = DEFAULT_NCACHE; } StarBlockFactory::~StarBlockFactory() { deleteBlocks(nBlocks); if (pInstance) pInstance = nullptr; } std::shared_ptr StarBlockFactory::getBlock() { std::shared_ptr freeBlock; if (nBlocks < nCache) { freeBlock.reset(new StarBlock); if (freeBlock.get()) { ++nBlocks; return freeBlock; } } if (last && (last->drawID != drawID || last->drawID == 0)) { // qCDebug(KSTARS) << "Recycling block with drawID =" << last->drawID << "and current drawID =" << drawID; if (last->parent->block(last->parent->getBlockCount() - 1) != last) qCDebug(KSTARS) << "ERROR: Goof up here!"; freeBlock = last; last = last->prev; if (last) { last->next = nullptr; } if (freeBlock == first) { first = nullptr; } freeBlock->reset(); freeBlock->prev = nullptr; freeBlock->next = nullptr; return freeBlock; } freeBlock.reset(new StarBlock); if (freeBlock.get()) ++nBlocks; return freeBlock; } bool StarBlockFactory::markFirst(std::shared_ptr& block) { if (!block.get()) return false; // fprintf(stderr, "markFirst()!\n"); if (!first) { // qCDebug(KSTARS) << "INFO: Linking in first block" << endl; last = first = block; first->prev = first->next = nullptr; first->drawID = drawID; return true; } if (block == first) // Block is already in the front { block->drawID = drawID; return true; } if (block == last) last = block->prev; if (block->prev) block->prev->next = block->next; if (block->next) block->next->prev = block->prev; first->prev = block; block->next = first; block->prev = nullptr; first = block; block->drawID = drawID; return true; } bool StarBlockFactory::markNext(std::shared_ptr& after, std::shared_ptr& block) { // fprintf(stderr, "markNext()!\n"); if (!block.get() || !after.get()) { qCDebug(KSTARS) << "WARNING: markNext called with nullptr argument" << endl; return false; } if (!first.get()) { qCDebug(KSTARS) << "WARNING: markNext called without an existing linked list" << endl; return false; } if (block == after) { qCDebug(KSTARS) << "ERROR: Trying to mark a block after itself!" << endl; return false; } if (block->prev == after) // Block is already after 'after' { block->drawID = drawID; return true; } if (block == first) { if (block->next == nullptr) { qCDebug(KSTARS) << "ERROR: Trying to mark only block after some other block"; return false; } first = block->next; } if (after->getFaintMag() > block->getFaintMag() && block->getFaintMag() != -5) { qCDebug(KSTARS) << "WARNING: Marking block with faint mag = " << block->getFaintMag() << " after block with faint mag " << after->getFaintMag() << "in trixel" << block->parent->getTrixel(); } if (block == last) last = block->prev; if (block->prev) block->prev->next = block->next; if (block->next) block->next->prev = block->prev; block->next = after->next; if (block->next) block->next->prev = block; block->prev = after; after->next = block; if (after == last) last = block; block->drawID = drawID; return true; } /* bool StarBlockFactory::groupMove( StarBlock *start, const int nblocks ) { StarBlock * end = nullptr; // Check for trivial cases if( !start || nblocks < 0 ) return false; if( nblocks == 0 ) return true; if( !first ) return false; // Check for premature end end = start; for( int i = 1; i < nblocks; ++i ) { if( end == nullptr ) return false; end = end->next; } if( end == nullptr ) return false; // Update drawIDs end = start; for( int i = 1; i < nblocks; ++i ) { end->drawID = drawID; end = end->next; } end->drawID = drawID; // Check if we are already in the front if( !start->prev ) return true; start->prev->next = end->next; end->next->prev = start->prev; first->prev = end; end->next = first; start->prev = nullptr; first = start; } */ int StarBlockFactory::deleteBlocks(int nblocks) { int i = 0; std::shared_ptr temp; while (last != nullptr && i != nblocks) { temp = last->prev; last.reset(); last = temp; i++; } if (last) last->next = nullptr; else first = nullptr; qCDebug(KSTARS) << nblocks << "StarBlocks freed from StarBlockFactory"; nBlocks -= i; return i; } void StarBlockFactory::printStructure() const { std::shared_ptr cur; Trixel curTrixel = 513; // TODO: Change if we change HTMesh level int index = 0; bool draw = false; cur = first; do { if (curTrixel != cur->parent->getTrixel()) { qCDebug(KSTARS) << "Trixel" << cur->parent->getTrixel() << "starts at index" << index << endl; curTrixel = cur->parent->getTrixel(); } if (cur->drawID == drawID && !draw) { qCDebug(KSTARS) << "Blocks from index" << index << "are drawn"; draw = true; } if (cur->drawID != drawID && draw) { qCDebug(KSTARS) << "Blocks from index" << index << "are not drawn"; draw = false; } cur = cur->next; ++index; } while (cur != last); } int StarBlockFactory::freeUnused() { int i = 0; std::shared_ptr temp; while (last != nullptr && last->drawID < drawID && i != nBlocks) { temp = last->prev; last.reset(); last = temp; i++; } if (last) last->next = nullptr; else first = nullptr; qCDebug(KSTARS) << i << "StarBlocks freed from StarBlockFactory"; nBlocks -= i; return i; } diff --git a/kstars/skycomponents/starcomponent.cpp b/kstars/skycomponents/starcomponent.cpp index 55639e5d5..f5673c4e3 100644 --- a/kstars/skycomponents/starcomponent.cpp +++ b/kstars/skycomponents/starcomponent.cpp @@ -1,844 +1,844 @@ /*************************************************************************** starcomponent.cpp - K Desktop Planetarium ------------------- begin : 2005/14/08 copyright : (C) 2005 by Thomas Kabelmann email : thomas.kabelmann@gmx.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. * * * ***************************************************************************/ #include "starcomponent.h" #include "binfilehelper.h" #include "deepstarcomponent.h" #include "highpmstarlist.h" #ifndef KSTARS_LITE #include "kstars.h" #endif #include "kstarsdata.h" #include "kstarssplash.h" #include "Options.h" #include "skylabeler.h" #include "skymap.h" #include "skymesh.h" #ifndef KSTARS_LITE #include "skyqpainter.h" #endif #include "htmesh/MeshIterator.h" #include "projections/projector.h" #include #ifdef _WIN32 #include #endif #if defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) #include #define bswap_16(x) bswap16(x) #define bswap_32(x) bswap32(x) #else #include "byteorder.h" #endif -StarComponent *StarComponent::pinstance = 0; +StarComponent *StarComponent::pinstance = nullptr; StarComponent::StarComponent(SkyComposite *parent) : ListComponent(parent), m_reindexNum(J2000) { m_skyMesh = SkyMesh::Instance(); m_StarBlockFactory = StarBlockFactory::Instance(); m_starIndex.reset(new StarIndex()); for (int i = 0; i < m_skyMesh->size(); i++) m_starIndex->append(new StarList()); m_highPMStars.append(new HighPMStarList(840.0)); m_highPMStars.append(new HighPMStarList(304.0)); m_reindexInterval = StarObject::reindexInterval(304.0); for (int i = 0; i <= MAX_LINENUMBER_MAG; i++) m_labelList[i] = new LabelList; // Actually load data emitProgressText(i18n("Loading stars")); loadStaticData(); // Load any deep star catalogs that are available loadDeepStarCatalogs(); // The following works but can cause crashes sometimes //QtConcurrent::run(this, &StarComponent::loadDeepStarCatalogs); //In KStars Lite star images are initialized in SkyMapLite #ifndef KSTARS_LITE SkyQPainter::initStarImages(); #endif } StarComponent::~StarComponent() { qDeleteAll(*m_starIndex); m_starIndex->clear(); qDeleteAll(m_DeepStarComponents); m_DeepStarComponents.clear(); qDeleteAll(m_highPMStars); m_highPMStars.clear(); for (int i = 0; i <= MAX_LINENUMBER_MAG; i++) delete m_labelList[i]; } StarComponent *StarComponent::Create(SkyComposite *parent) { delete pinstance; pinstance = new StarComponent(parent); return pinstance; } bool StarComponent::selected() { return Options::showStars(); } bool StarComponent::addDeepStarCatalogIfExists(const QString &fileName, float trigMag, bool staticstars) { if (BinFileHelper::testFileExists(fileName)) { m_DeepStarComponents.append(new DeepStarComponent(parent(), fileName, trigMag, staticstars)); return true; } return false; } int StarComponent::loadDeepStarCatalogs() { // Look for the basic unnamed star catalog to mag 8.0 if (!addDeepStarCatalogIfExists("unnamedstars.dat", -5.0, true)) return 0; // Look for the Tycho-2 add-on with 2.5 million stars to mag 12.5 if (!addDeepStarCatalogIfExists("tycho2.dat", 8.0) && !addDeepStarCatalogIfExists("deepstars.dat", 8.0)) return 1; // Look for the USNO NOMAD 1e8 star catalog add-on with stars to mag 16 if (!addDeepStarCatalogIfExists("USNO-NOMAD-1e8.dat", 11.0)) return 2; return 3; } //This function is empty for a reason; we override the normal //update function in favor of JiT updates for stars. void StarComponent::update(KSNumbers *) { } // We use the update hook to re-index all the stars when the date has changed by // more than 150 years. bool StarComponent::reindex(KSNumbers *num) { if (!num) return false; // for large time steps we re-index all points if (fabs(num->julianCenturies() - m_reindexNum.julianCenturies()) > m_reindexInterval) { reindexAll(num); return true; } bool highPM = true; // otherwise we just re-index fast movers as needed for (int j = 0; j < m_highPMStars.size(); j++) highPM &= !(m_highPMStars.at(j)->reindex(num, m_starIndex.get())); return !(highPM); } void StarComponent::reindexAll(KSNumbers *num) { if (0 && !m_reindexSplash) { m_reindexSplash = new KStarsSplash(i18n("Please wait while re-indexing stars...")); QObject::connect(KStarsData::Instance(), SIGNAL(progressText(QString)), m_reindexSplash, SLOT(setMessage(QString))); m_reindexSplash->show(); m_reindexSplash->raise(); return; } printf("Re-indexing Stars to year %4.1f...\n", 2000.0 + num->julianCenturies() * 100.0); m_reindexNum = KSNumbers(*num); m_skyMesh->setKSNumbers(num); // clear out the old index for (int i = 0; i < m_starIndex->size(); i++) { m_starIndex->at(i)->clear(); } // re-populate it from the objectList int size = m_ObjectList.size(); for (int i = 0; i < size; i++) { StarObject *star = (StarObject *)m_ObjectList[i]; Trixel trixel = m_skyMesh->indexStar(star); m_starIndex->at(trixel)->append(star); } // Let everyone else know we have re-indexed to num for (int j = 0; j < m_highPMStars.size(); j++) { m_highPMStars.at(j)->setIndexTime(num); } //delete m_reindexSplash; //m_reindexSplash = 0; printf("Done.\n"); } float StarComponent::faintMagnitude() const { float faintmag = m_FaintMagnitude; for (int i = 0; i < m_DeepStarComponents.size(); ++i) { if (faintmag < m_DeepStarComponents.at(i)->faintMagnitude()) faintmag = m_DeepStarComponents.at(i)->faintMagnitude(); } return faintmag; } float StarComponent::zoomMagnitudeLimit() { //adjust maglimit for ZoomLevel double lgmin = log10(MINZOOM); double lgz = log10(Options::zoomFactor()); // Old formula: // float maglim = ( 2.000 + 2.444 * Options::memUsage() / 10.0 ) * ( lgz - lgmin ) + Options::magLimitDrawStarZoomOut(); /* Explanation for the following formula: -------------------------------------- Estimates from a sample of 125000 stars shows that, magnitude limit vs. number of stars follows the formula: nStars = 10^(.45 * maglim + .95) (A better formula is available here: http://www.astro.uu.nl/~strous/AA/en/antwoorden/magnituden.html which we do not implement for simplicity) We want to keep the star density on screen a constant. This is directly proportional to the number of stars and directly proportional to the area on screen. The area is in turn inversely proportional to the square of the zoom factor ( zoomFactor / MINZOOM ). This means that (taking logarithms): 0.45 * maglim + 0.95 - 2 * log( ZoomFactor ) - log( Star Density ) - log( Some proportionality constant ) hence the formula. We've gathered together all the constants and set it to 3.5, so as to set the minimum possible value of maglim to 3.5 */ // float maglim = 4.444 * ( lgz - lgmin ) + 2.222 * log10( Options::starDensity() ) + 3.5; // Reducing the slope w.r.t zoom factor to avoid the extremely fast increase in star density with zoom // that 4.444 gives us (although that is what the derivation gives us) return 3.5 + 3.7 * (lgz - lgmin) + 2.222 * log10(static_cast(Options::starDensity())); } void StarComponent::draw(SkyPainter *skyp) { #ifndef KSTARS_LITE if (!selected()) return; SkyMap *map = SkyMap::Instance(); const Projector *proj = map->projector(); KStarsData *data = KStarsData::Instance(); UpdateID updateID = data->updateID(); bool checkSlewing = (map->isSlewing() && Options::hideOnSlew()); m_hideLabels = checkSlewing || !(Options::showStarMagnitudes() || Options::showStarNames()); //shortcuts to inform whether to draw different objects bool hideFaintStars = checkSlewing && Options::hideStars(); double hideStarsMag = Options::magLimitHideStar(); reindex(data->updateNum()); double lgmin = log10(MINZOOM); double lgmax = log10(MAXZOOM); double lgz = log10(Options::zoomFactor()); double maglim; m_zoomMagLimit = maglim = zoomMagnitudeLimit(); double labelMagLim = Options::starLabelDensity() / 5.0; labelMagLim += (12.0 - labelMagLim) * (lgz - lgmin) / (lgmax - lgmin); if (labelMagLim > 8.0) labelMagLim = 8.0; //Calculate sizeMagLim // Old formula: // float sizeMagLim = ( 2.000 + 2.444 * Options::memUsage() / 10.0 ) * ( lgz - lgmin ) + 5.8; // Using the maglim to compute the sizes of stars reduces // discernability between brighter and fainter stars at high zoom // levels. To fix that, we use an "arbitrary" constant in place of // the variable star density. // Not using this formula now. // float sizeMagLim = 4.444 * ( lgz - lgmin ) + 5.0; float sizeMagLim = zoomMagnitudeLimit(); if (sizeMagLim > faintMagnitude() * (1 - 1.5 / 16)) sizeMagLim = faintMagnitude() * (1 - 1.5 / 16); skyp->setSizeMagLimit(sizeMagLim); //Loop for drawing star images MeshIterator region(m_skyMesh, DRAW_BUF); magLim = maglim; // If we are hiding faint stars, then maglim is really the brighter of hideStarsMag and maglim if (hideFaintStars && maglim > hideStarsMag) maglim = hideStarsMag; m_StarBlockFactory->drawID = m_skyMesh->drawID(); int nTrixels = 0; while (region.hasNext()) { ++nTrixels; Trixel currentRegion = region.next(); StarList *starList = m_starIndex->at(currentRegion); for (int i = 0; i < starList->size(); ++i) { StarObject *curStar = starList->at(i); if (!curStar) continue; float mag = curStar->mag(); // break loop if maglim is reached if (mag > maglim) break; if (curStar->updateID != updateID) curStar->JITupdate(); bool drawn = skyp->drawPointSource(curStar, mag, curStar->spchar()); //FIXME_SKYPAINTER: find a better way to do this. if (drawn && !(m_hideLabels || mag > labelMagLim)) addLabel(proj->toScreen(curStar), curStar); } } // Draw focusStar if not null if (focusStar) { if (focusStar->updateID != updateID) focusStar->JITupdate(); float mag = focusStar->mag(); skyp->drawPointSource(focusStar, mag, focusStar->spchar()); } // Now draw each of our DeepStarComponents for (int i = 0; i < m_DeepStarComponents.size(); ++i) { m_DeepStarComponents.at(i)->draw(skyp); } #else Q_UNUSED(skyp) #endif } void StarComponent::addLabel(const QPointF &p, StarObject *star) { int idx = int(star->mag() * 10.0); if (idx < 0) idx = 0; if (idx > MAX_LINENUMBER_MAG) idx = MAX_LINENUMBER_MAG; m_labelList[idx]->append(SkyLabel(p, star)); } void StarComponent::drawLabels() { if (m_hideLabels) return; SkyLabeler *labeler = SkyLabeler::Instance(); labeler->setPen(QColor(KStarsData::Instance()->colorScheme()->colorNamed("SNameColor"))); int max = int(m_zoomMagLimit * 10.0); if (max < 0) max = 0; if (max > MAX_LINENUMBER_MAG) max = MAX_LINENUMBER_MAG; for (int i = 0; i <= max; i++) { LabelList *list = m_labelList[i]; for (int j = 0; j < list->size(); j++) { labeler->drawNameLabel(list->at(j).obj, list->at(j).o); } list->clear(); } } bool StarComponent::loadStaticData() { // We break from Qt / KDE API and use traditional file handling here, to obtain speed. // We also avoid C++ constructors for the same reason. FILE *dataFile, *nameFile; bool swapBytes = false, named = false, gnamed = false; BinFileHelper dataReader, nameReader; QString name, gname, visibleName; StarObject *star; if (starsLoaded) return true; // prepare to index stars to this date m_skyMesh->setKSNumbers(&m_reindexNum); /* Open the data files */ // TODO: Maybe we don't want to hardcode the filename? if ((dataFile = dataReader.openFile("namedstars.dat")) == nullptr) { qDebug() << "Could not open data file namedstars.dat" << endl; return false; } if (!(nameFile = nameReader.openFile("starnames.dat"))) { qDebug() << "Could not open data file starnames.dat" << endl; return false; } if (!dataReader.readHeader()) { qDebug() << "Error reading namedstars.dat header : " << dataReader.getErrorNumber() << " : " << dataReader.getError() << endl; return false; } if (!nameReader.readHeader()) { qDebug() << "Error reading starnames.dat header : " << nameReader.getErrorNumber() << " : " << nameReader.getError() << endl; return false; } //KDE_fseek(nameFile, nameReader.getDataOffset(), SEEK_SET); QT_FSEEK(nameFile, nameReader.getDataOffset(), SEEK_SET); swapBytes = dataReader.getByteSwap(); long int nstars = 0; //KDE_fseek(dataFile, dataReader.getDataOffset(), SEEK_SET); QT_FSEEK(dataFile, dataReader.getDataOffset(), SEEK_SET); qint16 faintmag; quint8 htm_level; quint16 t_MSpT; int ret = 0; ret = fread(&faintmag, 2, 1, dataFile); if (swapBytes) faintmag = bswap_16(faintmag); ret = fread(&htm_level, 1, 1, dataFile); ret = fread(&t_MSpT, 2, 1, dataFile); // Unused if (swapBytes) faintmag = bswap_16(faintmag); if (faintmag / 100.0 > m_FaintMagnitude) m_FaintMagnitude = faintmag / 100.0; if (htm_level != m_skyMesh->level()) qDebug() << "WARNING: HTM Level in shallow star data file and HTM Level in m_skyMesh do not match. EXPECT TROUBLE" << endl; for (int i = 0; i < m_skyMesh->size(); ++i) { Trixel trixel = i; // = ( ( i >= 256 ) ? ( i - 256 ) : ( i + 256 ) ); for (unsigned long j = 0; j < (unsigned long)dataReader.getRecordCount(i); ++j) { if (!fread(&stardata, sizeof(StarData), 1, dataFile)) { qDebug() << "FILE FORMAT ERROR: Could not read StarData structure for star #" << j << " under trixel #" << trixel << endl; } /* Swap Bytes when required */ if (swapBytes) byteSwap(&stardata); named = false; gnamed = false; /* Named Star - Read the nameFile */ if (stardata.flags & 0x01) { visibleName = ""; if (!fread(&starname, sizeof(starName), 1, nameFile)) qDebug() << "ERROR: fread() call on nameFile failed in trixel " << trixel << " star " << j << endl; name = QByteArray(starname.longName, 32); named = !name.isEmpty(); gname = QByteArray(starname.bayerName, 8); gnamed = !gname.isEmpty(); if (gnamed && starname.bayerName[0] != '.') visibleName = gname; if (named) { // HEV: look up star name in internationalization filesource name = i18nc("star name", name.toLocal8Bit().data()); } else { name = i18n("star"); } } else qDebug() << "ERROR: Named star file contains unnamed stars! Expect trouble." << endl; /* Create the new StarObject */ star = new StarObject; star->init(&stardata); //if( star->getHDIndex() != 0 && name == i18n("star")) if (stardata.HD) { m_HDHash.insert(stardata.HD, star); if (named == false) { name = QString("HD %1").arg(stardata.HD); named = true; } } star->setNames(name, visibleName); //star->EquatorialToHorizontal( data->lst(), data->geo()->lat() ); ++nstars; if (gnamed) m_genName.insert(gname, star); //if ( ! name.isEmpty() && name != i18n("star")) if (named) { objectNames(SkyObject::STAR).append(name); objectLists(SkyObject::STAR).append(QPair(name, star)); } if (!visibleName.isEmpty() && gname != name) { QString gName = star->gname(false); objectNames(SkyObject::STAR).append(gName); objectLists(SkyObject::STAR).append(QPair(gName, star)); } m_ObjectList.append(star); m_starIndex->at(trixel)->append(star); double pm = star->pmMagnitude(); for (int z = 0; z < m_highPMStars.size(); z++) { HighPMStarList *list = m_highPMStars.at(z); if (list->append(trixel, star, pm)) break; } } } dataReader.closeFile(); nameReader.closeFile(); starsLoaded = true; return true; } SkyObject *StarComponent::findStarByGenetiveName(const QString name) { if (name.startsWith(QLatin1String("HD"))) { QStringList fields = name.split(' ', QString::SkipEmptyParts); bool Ok = false; unsigned int HDNum = fields[1].toInt(&Ok); if (Ok) return findByHDIndex(HDNum); } return m_genName.value(name); } // Overrides ListComponent::findByName() to include genetive name and HD index also in the search SkyObject *StarComponent::findByName(const QString &name) { #ifndef KSTARS_LITE if (KStars::Closing) return nullptr; #endif foreach (SkyObject *o, m_ObjectList) { #ifndef KSTARS_LITE if (KStars::Closing) return nullptr; #endif if (QString::compare(o->name(), name, Qt::CaseInsensitive) == 0 || QString::compare(o->longname(), name, Qt::CaseInsensitive) == 0 || QString::compare(o->name2(), name, Qt::CaseInsensitive) == 0 || QString::compare(((StarObject *)o)->gname(false), name, Qt::CaseInsensitive) == 0) return o; } return nullptr; } void StarComponent::objectsInArea(QList &list, const SkyRegion ®ion) { for (SkyRegion::const_iterator it = region.constBegin(); it != region.constEnd(); ++it) { Trixel trixel = it.key(); StarList *starlist = m_starIndex->at(trixel); for (int i = 0; starlist && i < starlist->size(); i++) if (starlist->at(i) && starlist->at(i)->name() != QString("star")) list.push_back(starlist->at(i)); } } StarObject *StarComponent::findByHDIndex(int HDnum) { KStarsData *data = KStarsData::Instance(); StarObject *o = nullptr; BinFileHelper hdidxReader; // First check the hash to see if we have a corresponding StarObject already if ((o = m_HDHash.value(HDnum, nullptr))) return o; // If we don't have the StarObject here, try it in the DeepStarComponents' hashes if (m_DeepStarComponents.size() >= 1) if ((o = m_DeepStarComponents.at(0)->findByHDIndex(HDnum))) return o; if (m_DeepStarComponents.size() >= 2) { qint32 offset = 0; int ret = 0; FILE *hdidxFile = hdidxReader.openFile("Henry-Draper.idx"); FILE *dataFile = nullptr; if (!hdidxFile) - return 0; + return nullptr; //KDE_fseek( hdidxFile, (HDnum - 1) * 4, SEEK_SET ); QT_FSEEK(hdidxFile, (HDnum - 1) * 4, SEEK_SET); // TODO: Offsets need to be byteswapped if this is a big endian machine. // This means that the Henry Draper Index needs a endianness indicator. ret = fread(&offset, 4, 1, hdidxFile); if (offset <= 0) - return 0; + return nullptr; dataFile = m_DeepStarComponents.at(1)->getStarReader()->getFileHandle(); //KDE_fseek( dataFile, offset, SEEK_SET ); QT_FSEEK(dataFile, offset, SEEK_SET); ret = fread(&stardata, sizeof(StarData), 1, dataFile); if (m_DeepStarComponents.at(1)->getStarReader()->getByteSwap()) { byteSwap(&stardata); } m_starObject.init(&stardata); m_starObject.EquatorialToHorizontal(data->lst(), data->geo()->lat()); m_starObject.JITupdate(); focusStar = m_starObject.clone(); m_HDHash.insert(HDnum, focusStar); hdidxReader.closeFile(); return focusStar; } - return 0; + return nullptr; } // This uses the main star index for looking up nearby stars but then // filters out objects with the generic name "star". We could easily // build an index for just the named stars which would make this go // much faster still. -jbb // SkyObject *StarComponent::objectNearest(SkyPoint *p, double &maxrad) { m_zoomMagLimit = zoomMagnitudeLimit(); - SkyObject *oBest = 0; + SkyObject *oBest = nullptr; MeshIterator region(m_skyMesh, OBJ_NEAREST_BUF); while (region.hasNext()) { Trixel currentRegion = region.next(); StarList *starList = m_starIndex->at(currentRegion); for (int i = 0; i < starList->size(); ++i) { StarObject *star = starList->at(i); if (!star) continue; if (star->mag() > m_zoomMagLimit) continue; double r = star->angularDistanceTo(p).Degrees(); if (r < maxrad) { oBest = star; maxrad = r; } } } // Check up with our Deep Star Components too! double rTry, rBest; SkyObject *oTry; // JM 2016-03-30: Multiply rBest by a factor of 0.5 so that named stars are preferred to unnamed stars searched below rBest = maxrad * 0.5; rTry = maxrad; for (int i = 0; i < m_DeepStarComponents.size(); ++i) { oTry = m_DeepStarComponents.at(i)->objectNearest(p, rTry); if (rTry < rBest) { rBest = rTry; oBest = oTry; } } maxrad = rBest; return oBest; } void StarComponent::starsInAperture(QList &list, const SkyPoint ¢er, float radius, float maglim) { // Ensure that we have deprecessed the (RA, Dec) to (RA0, Dec0) Q_ASSERT(center.ra0().Degrees() >= 0.0); Q_ASSERT(center.dec0().Degrees() <= 90.0); m_skyMesh->intersect(center.ra0().Degrees(), center.dec0().Degrees(), radius, (BufNum)OBJ_NEAREST_BUF); MeshIterator region(m_skyMesh, OBJ_NEAREST_BUF); if (maglim < -28) maglim = m_FaintMagnitude; while (region.hasNext()) { Trixel currentRegion = region.next(); StarList *starList = m_starIndex->at(currentRegion); for (int i = 0; i < starList->size(); ++i) { StarObject *star = starList->at(i); if (!star) continue; if (star->mag() > m_FaintMagnitude) continue; if (star->angularDistanceTo(¢er).Degrees() <= radius) list.append(star); } } // Add stars from the DeepStarComponents as well for (int i = 0; i < m_DeepStarComponents.size(); ++i) { m_DeepStarComponents.at(i)->starsInAperture(list, center, radius, maglim); } } void StarComponent::byteSwap(StarData *stardata) { stardata->RA = bswap_32(stardata->RA); stardata->Dec = bswap_32(stardata->Dec); stardata->dRA = bswap_32(stardata->dRA); stardata->dDec = bswap_32(stardata->dDec); stardata->parallax = bswap_32(stardata->parallax); stardata->HD = bswap_32(stardata->HD); stardata->mag = bswap_16(stardata->mag); stardata->bv_index = bswap_16(stardata->bv_index); } /* void StarComponent::printDebugInfo() { int nTrixels = 0; int nBlocks = 0; long int nStars = 0; float faintMag = -5.0; MeshIterator trixels( m_skyMesh, DRAW_BUF ); Trixel trixel; while( trixels.hasNext() ) { trixel = trixels.next(); nTrixels++; for(int i = 0; i < m_starBlockList[ trixel ]->getBlockCount(); ++i) { nBlocks++; StarBlock *block = m_starBlockList[ trixel ]->block( i ); for(int j = 0; j < block->getStarCount(); ++j) { nStars++; } if( block->getFaintMag() > faintMag ) { faintMag = block->getFaintMag(); } } } printf( "========== UNNAMED STAR MEMORY ALLOCATION INFORMATION ==========\n" ); printf( "Number of visible trixels = %8d\n", nTrixels ); printf( "Number of visible StarBlocks = %8d\n", nBlocks ); printf( "Number of StarBlocks allocated via SBF = %8d\n", m_StarBlockFactory.getBlockCount() ); printf( "Number of unnamed stars in memory = %8ld\n", nStars ); printf( "Magnitude of the faintest star in memory = %8.2f\n", faintMag ); printf( "Target magnitude limit = %8.2f\n", magLim ); printf( "Size of each StarBlock = %8d bytes\n", sizeof( StarBlock ) ); printf( "Size of each StarObject = %8d bytes\n", sizeof( StarObject ) ); printf( "Memory use due to visible unnamed stars = %8.2f MB\n", ( sizeof( StarObject ) * nStars / 1048576.0 ) ); printf( "Memory use due to visible StarBlocks = %8d bytes\n", sizeof( StarBlock ) * nBlocks ); printf( "Memory use due to StarBlocks in SBF = %8d bytes\n", sizeof( StarBlock ) * m_StarBlockFactory.getBlockCount() ); printf( "=============== STAR DRAW LOOP TIMING INFORMATION ==============\n" ); printf( "Time taken for drawing named stars = %8ld ms\n", t_drawNamed ); printf( "Time taken for dynamic load of data = %8ld ms\n", t_dynamicLoad ); printf( "Time taken for updating LRU cache = %8ld ms\n", t_updateCache ); printf( "Time taken for drawing unnamed stars = %8ld ms\n", t_drawUnnamed ); printf( "================================================================\n" ); } bool StarComponent::verifySBLIntegrity() { float faintMag = -5.0; bool integrity = true; for(Trixel trixel = 0; trixel < m_skyMesh->size(); ++trixel) { for(int i = 0; i < m_starBlockList[ trixel ]->getBlockCount(); ++i) { StarBlock *block = m_starBlockList[ trixel ]->block( i ); if( i == 0 ) faintMag = block->getBrightMag(); // NOTE: Assumes 2 decimal places in magnitude field. TODO: Change if it ever does change if( block->getBrightMag() != faintMag && ( block->getBrightMag() - faintMag ) > 0.016) { qDebug() << "Trixel " << trixel << ": ERROR: faintMag of prev block = " << faintMag << ", brightMag of block #" << i << " = " << block->getBrightMag(); integrity = false; } if( i > 1 && ( !block->prev ) ) qDebug() << "Trixel " << trixel << ": ERROR: Block" << i << "is unlinked in LRU Cache"; if( block->prev && block->prev->parent == m_starBlockList[ trixel ] && block->prev != m_starBlockList[ trixel ]->block( i - 1 ) ) { qDebug() << "Trixel " << trixel << ": ERROR: SBF LRU Cache linked list seems to be broken at before block " << i << endl; integrity = false; } faintMag = block->getFaintMag(); } } return integrity; } */ diff --git a/kstars/skycomponents/supernovaecomponent.cpp b/kstars/skycomponents/supernovaecomponent.cpp index 399121d69..662e9f55b 100644 --- a/kstars/skycomponents/supernovaecomponent.cpp +++ b/kstars/skycomponents/supernovaecomponent.cpp @@ -1,273 +1,273 @@ /* Supernova Component Copyright (C) 2016 Jasem Mutlaq Based on Samikshan Bairagya GSoC work. 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 "supernovaecomponent.h" #include #include #include #ifndef KSTARS_LITE #include "skymap.h" #else #include "kstarslite.h" #endif #include "skypainter.h" #include "skymesh.h" #include "skylabeler.h" #include "projections/projector.h" #include "dms.h" #include "Options.h" #include "auxiliary/filedownloader.h" #include "ksnotification.h" //#include "notifyupdatesui.h" #ifndef KSTARS_LITE #include "kstars.h" #endif #include "kstarsdata.h" #include "auxiliary/kspaths.h" SupernovaeComponent::SupernovaeComponent(SkyComposite *parent) : ListComponent(parent) { QtConcurrent::run(this, &SupernovaeComponent::loadData); //loadData(); } SupernovaeComponent::~SupernovaeComponent() { } void SupernovaeComponent::update(KSNumbers *num) { if (!selected()) return; KStarsData *data = KStarsData::Instance(); foreach (SkyObject *so, m_ObjectList) { if (num) so->updateCoords(num); so->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } } bool SupernovaeComponent::selected() { return Options::showSupernovae(); } void SupernovaeComponent::loadData() { qDeleteAll(m_ObjectList); m_ObjectList.clear(); objectNames(SkyObject::SUPERNOVA).clear(); objectLists(SkyObject::SUPERNOVA).clear(); QString name, type, host, date, ra, de; float z, mag; QString sFileName = KSPaths::locate(QStandardPaths::GenericDataLocation, QString("catalog.min.json")); QFile sNovaFile(sFileName); if (sNovaFile.open(QIODevice::ReadOnly) == false) { qCritical() << "Unable to open supernova file" << sFileName; return; } QJsonParseError pError; QJsonDocument sNova = QJsonDocument::fromJson(sNovaFile.readAll(), &pError); if (pError.error != QJsonParseError::NoError) { qCritical() << "Error parsing json document" << pError.errorString(); return; } if (sNova.isArray() == false) { qCritical() << "Invalid document format! No JSON array."; return; } QJsonArray sArray = sNova.array(); bool ok = false; foreach (const QJsonValue &snValue, sArray) { const QJsonObject propObject = snValue.toObject(); mag = 99.9; z = 0; name.clear(); type.clear(); host.clear(); date.clear(); if (propObject.contains("ra") == false || propObject.contains("dec") == false) continue; ra = ((propObject["ra"].toArray()[0]).toObject()["value"]).toString(); de = ((propObject["dec"].toArray()[0]).toObject()["value"]).toString(); name = propObject["name"].toString(); if (propObject.contains("claimedtype")) type = ((propObject["claimedtype"].toArray()[0]).toObject()["value"]).toString(); if (propObject.contains("host")) host = ((propObject["host"].toArray()[0]).toObject()["value"]).toString(); if (propObject.contains("discoverdate")) date = ((propObject["discoverdate"].toArray()[0]).toObject()["value"]).toString(); if (propObject.contains("redshift")) z = ((propObject["redshift"].toArray()[0]).toObject()["value"]).toString().toDouble(&ok); if (ok == false) z = 99.9; if (propObject.contains("maxappmag")) mag = ((propObject["maxappmag"].toArray()[0]).toObject()["value"]).toString().toDouble(&ok); if (ok == false) mag = 99.9; Supernova *sup = new Supernova(name, dms::fromString(ra, false), dms::fromString(de, true), type, host, date, z, mag); objectNames(SkyObject::SUPERNOVA).append(name); m_ObjectList.append(sup); objectLists(SkyObject::SUPERNOVA).append(QPair(name, sup)); } } SkyObject *SupernovaeComponent::findByName(const QString &name) { foreach (SkyObject *o, m_ObjectList) { if (QString::compare(o->name(), name, Qt::CaseInsensitive) == 0) return o; } //if no object is found then.. return nullptr; } SkyObject *SupernovaeComponent::objectNearest(SkyPoint *p, double &maxrad) { - SkyObject *oBest = 0; + SkyObject *oBest = nullptr; double rBest = maxrad; foreach (SkyObject *so, m_ObjectList) { double r = so->angularDistanceTo(p).Degrees(); //qDebug()<(Options::starDensity())); } void SupernovaeComponent::draw(SkyPainter *skyp) { //qDebug()<<"selected()="<mag(); if (mag > float(Options::magnitudeLimitShowSupernovae())) continue; //Do not draw if mag>maglim if (mag > maglim) continue; skyp->drawSupernova(sup); } } #if 0 void SupernovaeComponent::notifyNewSupernovae() { #ifndef KSTARS_LITE //qDebug()<<"New Supernovae discovered"; QList latestList; foreach (SkyObject * so, latest) { Supernova * sup = (Supernova *)so; if (sup->getMagnitude() > float(Options::magnitudeLimitAlertSupernovae())) { //qDebug()<<"Not Bright enough to be notified"; continue; } //qDebug()<<"Bright enough to be notified"; latestList.append(so); } if (!latestList.empty()) { NotifyUpdatesUI * ui = new NotifyUpdatesUI(0); ui->addItems(latestList); ui->show(); } // if (!latest.empty()) // KMessageBox::informationList(0, i18n("New Supernovae discovered!"), latestList, i18n("New Supernovae discovered!")); #endif } #endif void SupernovaeComponent::slotTriggerDataFileUpdate() { downloadJob = new FileDownloader(); downloadJob->setProgressDialogEnabled(true, i18n("Supernovae Update"), i18n("Downloading Supernovae updates...")); QObject::connect(downloadJob, SIGNAL(downloaded()), this, SLOT(downloadReady())); QObject::connect(downloadJob, SIGNAL(error(QString)), this, SLOT(downloadError(QString))); QString output = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "catalog.min.json"; downloadJob->setDownloadedFileURL(QUrl::fromLocalFile(output)); downloadJob->get(QUrl("https://sne.space/astrocats/astrocats/supernovae/output/catalog.min.json")); } void SupernovaeComponent::downloadReady() { // Reload Supernova loadData(); #ifdef KSTARS_LITE KStarsLite::Instance()->data()->setFullTimeUpdate(); #else KStars::Instance()->data()->setFullTimeUpdate(); #endif downloadJob->deleteLater(); } void SupernovaeComponent::downloadError(const QString &errorString) { KSNotification::error(i18n("Error downloading asteroids data: %1", errorString)); downloadJob->deleteLater(); } diff --git a/kstars/skycomponents/supernovaecomponent.h b/kstars/skycomponents/supernovaecomponent.h index e76aefdee..7dbb45777 100644 --- a/kstars/skycomponents/supernovaecomponent.h +++ b/kstars/skycomponents/supernovaecomponent.h @@ -1,73 +1,73 @@ /* Supernova Component Copyright (C) 2016 Jasem Mutlaq Based on Samikshan Bairagya GSoC work. 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. */ #ifndef SUPERNOVAE_COMPONENT_H #define SUPERNOVAE_COMPONENT_H #include "listcomponent.h" #include "skyobjects/supernova.h" #include "ksnumbers.h" #include /** * @class SupernovaeComponent This class encapsulates Supernovae. * * @author Jasem Mutlaq, Samikshan Bairagya * * @version 0.2 */ class Supernova; class FileDownloader; class SupernovaeComponent : public QObject, public ListComponent { Q_OBJECT public: explicit SupernovaeComponent(SkyComposite *parent); ~SupernovaeComponent() override; bool selected() override; - void update(KSNumbers *num = 0) override; + void update(KSNumbers *num = nullptr) override; SkyObject *findByName(const QString &name) override; SkyObject *objectNearest(SkyPoint *p, double &maxrad) override; /** * @note This should actually be implemented in a better manner. * Possibly by checking if the host galaxy for the supernova is drawn. */ void draw(SkyPainter *skyp) override; //virtual void notifyNewSupernovae(); /** * @note Basically copy pasted from StarComponent::zoomMagnitudeLimit() */ static float zoomMagnitudeLimit(); public slots: /** * @short This initiates updating of the data file */ void slotTriggerDataFileUpdate(); protected slots: void downloadReady(); void downloadError(const QString &errorString); private: void loadData(); FileDownloader *downloadJob = nullptr; }; #endif diff --git a/kstars/skycomponents/syncedcatalogcomponent.cpp b/kstars/skycomponents/syncedcatalogcomponent.cpp index 82004e0a5..a71b01628 100644 --- a/kstars/skycomponents/syncedcatalogcomponent.cpp +++ b/kstars/skycomponents/syncedcatalogcomponent.cpp @@ -1,115 +1,115 @@ /*************************************************************************** syncedcatalogcomponent.cpp - K Desktop Planetarium ------------------- begin : Tue 16 Aug 2016 04:19:00 CDT copyright : (c) 2016 by Akarsh Simha email : akarsh.simha@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ /* Project Includes */ #include "syncedcatalogcomponent.h" #include "kstarsdata.h" #include "deepskyobject.h" #include "Options.h" #include "catalogdata.h" /* KDE Includes */ /* Qt Includes */ SyncedCatalogComponent::SyncedCatalogComponent(SkyComposite *parent, const QString &catname, bool showerrs, int index) : CatalogComponent(parent, catname, showerrs, index, false) { // First check if the catalog exists CatalogDB *db = KStarsData::Instance()->catalogdb(); Q_ASSERT(db); m_catId = db->FindCatalog(catname); if (m_catId >= 0) loadData(); else { // Create the catalog qWarning() << "Creating new catalog " << catname; CatalogData catData; catData.color = "#ff0000"; // FIXME: Allow users to change the color of these catalogs (eg: for resolved objects) catData.epoch = 2000.0; catData.fluxfreq = "400 nm"; catData.fluxunit = "mag"; catData.author = "KStars"; catData.license = "Unknown"; catData.catalog_name = catname; catData.prefix = catname; db->AddCatalog(catData); m_catId = db->FindCatalog(catname); loadData(); Options::setShowCatalogNames(Options::showCatalogNames() << catname); } Q_ASSERT(m_catId >= 0); m_catColor = "#ff0000"; // FIXME: HARDCODED! m_catCount = m_ObjectList.count(); } /* SyncedCatalogComponent::~SyncedCatalogComponent() { } */ /* void SyncedCatalogComponent::draw( SkyPainter *skyp ) { qDebug() << "in SyncedCatalogComponent::draw()!"; CatalogComponent::draw( skyp ); } */ DeepSkyObject *SyncedCatalogComponent::addObject(CatalogEntryData catalogEntry) { if (std::isnan(catalogEntry.major_axis)) catalogEntry.major_axis = 0.0; if (std::isnan(catalogEntry.minor_axis)) catalogEntry.minor_axis = 0.0; CatalogEntryData dbEntry = catalogEntry; if (dbEntry.catalog_name != m_catName) { qWarning() << "Trying to add object " << catalogEntry.catalog_name << catalogEntry.ID << " to catalog " << m_catName << " will over-write catalog name with " << m_catName << " in the database and assign an arbitrary ID"; dbEntry.catalog_name = m_catName; } dbEntry.ID = m_catCount; CatalogDB *db = KStarsData::Instance()->catalogdb(); if (!(db->AddEntry(dbEntry, m_catId))) - return 0; + return nullptr; m_catCount++; qDebug() << "Added object " << catalogEntry.long_name << " into database!"; DeepSkyObject *newObj = new DeepSkyObject( catalogEntry, this); // FIXME: What about stars? Are they treated as DeepSkyObjects, type CATALOG_STAR? -- asimha Q_ASSERT(newObj); qDebug() << "Created new DSO for " << catalogEntry.long_name; if (newObj->hasLongName()) { // newObj->setName( newObj->longname() ); objectNames()[newObj->type()].append(newObj->longname()); objectLists()[newObj->type()].append(QPair(newObj->longname(), newObj)); } else { qWarning() << "Created object with name " << newObj->name() << " which is probably fake!"; objectNames()[newObj->type()].append(newObj->name()); objectLists()[newObj->type()].append(QPair(newObj->name(), newObj)); } m_ObjectList.append(newObj); qDebug() << "Added new SkyObject " << newObj->name() << " to synced catalog " << m_catName << " which now contains " << m_ObjectList.count() << " objects."; return newObj; } diff --git a/kstars/skycomponents/targetlistcomponent.cpp b/kstars/skycomponents/targetlistcomponent.cpp index 11e5b3b31..b638672aa 100644 --- a/kstars/skycomponents/targetlistcomponent.cpp +++ b/kstars/skycomponents/targetlistcomponent.cpp @@ -1,56 +1,56 @@ /*************************************************************************** targetlistcomponent.cpp - K Desktop Planetarium ------------------- begin : Oct 14 2010 9:59 PM CDT copyright : (C) 2010 Akarsh Simha email : akarsh.simha@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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 "targetlistcomponent.h" #ifndef KSTARS_LITE #include "skymap.h" #endif #include "skypainter.h" TargetListComponent::TargetListComponent(SkyComposite *parent) : SkyComponent(parent) { - drawSymbols = 0; - drawLabels = 0; + drawSymbols = nullptr; + drawLabels = nullptr; } TargetListComponent::TargetListComponent(SkyComposite *parent, SkyObjectList *objectList, QPen _pen, bool (*optionDrawSymbols)(void), bool (*optionDrawLabels)(void)) : SkyComponent(parent), list(objectList), pen(_pen) { drawSymbols = optionDrawSymbols; drawLabels = optionDrawLabels; } TargetListComponent::~TargetListComponent() { if (list.get()) { qDeleteAll(*list); } } void TargetListComponent::draw(SkyPainter *skyp) { if (drawSymbols && !(*drawSymbols)()) return; if (!list || list->count() <= 0) return; skyp->setPen(pen); skyp->drawObservingList(*list); } diff --git a/kstars/skycomponents/targetlistcomponent.h b/kstars/skycomponents/targetlistcomponent.h index 92d48db97..c432a84bc 100644 --- a/kstars/skycomponents/targetlistcomponent.h +++ b/kstars/skycomponents/targetlistcomponent.h @@ -1,88 +1,88 @@ /*************************************************************************** targetlistcomponent.h - K Desktop Planetarium ------------------- begin : Oct 14 2010 9:59 PM CDT copyright : (C) 2010 Akarsh Simha email : akarsh.simha@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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 "skycomponent.h" #include /** * @class TargetListComponent * @short Highlights objects present in certain lists by drawing "target" symbols around them. * * To represent lists of specific objects on the skymap (eg: A star * hopping route, or a list of planned observation targets), one would * typically like to draw over the skymap, a bunch of symbols * highlighting the locations of these objects. This class manages * drawing of such lists. Along with the list, we also associate a pen * to use to represent objects from that list. Once this class is made * aware of a list to plot (which is stored somewhere else), it does * so when called from the SkyMapComposite. The class may be supplied * with pointers to two methods that tell it whether to draw symbols / * labels or not. Disabling symbol drawing is equivalent to disabling * the list. If the pointers are nullptr, the symbols are always drawn, * but the labels are not drawn. * * @note This does not inherit from ListComponent because it is not * necessary. ListComponent has extra methods like objectNearest(), * which we don't want. Also, ListComponent maintains its own list, * whereas this class merely holds a pointer to a list that's * manipulated from elsewhere. */ class TargetListComponent : public SkyComponent { public: /** * @short Default constructor. */ explicit TargetListComponent(SkyComposite *parent); /** * @short Constructor that sets up this target list */ TargetListComponent(SkyComposite *parent, QList *objectList, QPen _pen, - bool (*optionDrawSymbols)(void) = 0, bool (*optionDrawLabels)(void) = 0); + bool (*optionDrawSymbols)(void) = nullptr, bool (*optionDrawLabels)(void) = nullptr); ~TargetListComponent() override; /** * @short Draw this component by iterating over the list. * * @note This method does not bother refreshing the coordinates of * the objects on the list. So this must be called only after the * objects are drawn in a given draw cycle. */ void draw(SkyPainter *skyp) override; // FIXME: Maybe we should make these member objects private / protected? std::unique_ptr list; // Pointer to list of objects to draw QPen pen; // Pen to use to draw /** * @short Pointer to static method that tells us whether to draw this list or not * @note If the pointer is nullptr, the list is drawn nevertheless */ bool (*drawSymbols)(void); /** * @short Pointer to static method that tells us whether to draw labels for this list or not * @note If the pointer is nullptr, labels are not drawn */ bool (*drawLabels)(void); }; diff --git a/kstars/skyglpainter.h b/kstars/skyglpainter.h index 563dd2afa..a475bd6d9 100644 --- a/kstars/skyglpainter.h +++ b/kstars/skyglpainter.h @@ -1,81 +1,81 @@ /* 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. */ #ifndef SKYGLPAINTER_H #define SKYGLPAINTER_H #include #include using namespace Eigen; #include "skypainter.h" #include "skyobjects/skyobject.h" #include "projections/projector.h" class QGLWidget; class SkyGLPainter : public SkyPainter { public: explicit SkyGLPainter(QGLWidget *widget); bool drawPlanet(KSPlanetBase *planet) override; bool drawDeepSkyObject(DeepSkyObject *obj, bool drawImage = false) override; bool drawPointSource(SkyPoint *loc, float mag, char sp = 'A') override; void drawSkyPolygon(LineList *list, bool forceClip = true) override; - void drawSkyPolyline(LineList *list, SkipHashList *skipList = 0, LineListLabel *label = 0) override; + void drawSkyPolyline(LineList *list, SkipHashList *skipList = nullptr, LineListLabel *label = nullptr) override; void drawSkyLine(SkyPoint *a, SkyPoint *b) override; void drawSkyBackground() override; void drawObservingList(const QList &obs) override; void drawFlags() override; void end() override; void begin() override; void setBrush(const QBrush &brush) override; void setPen(const QPen &pen) override; - void drawHorizon(bool filled, SkyPoint *labelPoint = 0, bool *drawLabel = 0) override; + void drawHorizon(bool filled, SkyPoint *labelPoint = nullptr, bool *drawLabel = nullptr) override; bool drawSatellite(Satellite *sat) override; bool drawSupernova(Supernova *sup) override; void drawText(int x, int y, const QString text, QFont font, QColor color); bool drawConstellationArtImage(ConstellationsArt *obj) override; bool drawHips() override; private: bool addItem(SkyPoint *p, int type, float width, char sp = 'a'); void drawBuffer(int type); void drawPolygon(const QVector &poly, bool convex = true, bool flush_buffers = true); /** Render textured rectangle on screeen. Parameters are texture * to be used, position, orientation and size of rectangle*/ void drawTexturedRectangle(const QImage &img, const Vector2f &pos, const float angle, const float sizeX, const float sizeY); const Projector *m_proj; Vector4f m_pen; static const int BUFSIZE = 512; ///FIXME: what kind of TYPE_UNKNOWN objects are there? static const int NUMTYPES = (int)SkyObject::TYPE_UNKNOWN + 1; static Vector2f m_vertex[NUMTYPES][6 * BUFSIZE]; static Vector2f m_texcoord[NUMTYPES][6 * BUFSIZE]; static Vector3f m_color[NUMTYPES][6 * BUFSIZE]; static int m_idx[NUMTYPES]; static bool m_init; ///< keep track of whether we have filled the texcoord array QGLWidget *m_widget; // Pointer to (GL) widget on which we are painting }; #endif // SKYGLPAINTER_H diff --git a/kstars/skymap.cpp b/kstars/skymap.cpp index 8581d9c54..71674563a 100644 --- a/kstars/skymap.cpp +++ b/kstars/skymap.cpp @@ -1,1419 +1,1419 @@ /************************************************************************** skymap.cpp - K Desktop Planetarium ------------------- begin : Sat Feb 10 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. * * * ***************************************************************************/ #ifdef _WIN32 #include #endif #include "skymap.h" #include "fov.h" #include "imageviewer.h" #include "ksdssdownloader.h" #include "kspaths.h" #include "kspopupmenu.h" #include "kstars.h" #include "ksutils.h" #include "Options.h" #include "skymapcomposite.h" #ifdef HAVE_OPENGL #include "skymapgldraw.h" #endif #include "skymapqdraw.h" #include "starhopperdialog.h" #include "starobject.h" #include "texturemanager.h" #include "dialogs/detaildialog.h" #include "printing/printingwizard.h" #include "skycomponents/flagcomponent.h" #include "skyobjects/deepskyobject.h" #include "skyobjects/ksplanetbase.h" #include "tools/flagmanager.h" #include "widgets/infoboxwidget.h" #include "projections/azimuthalequidistantprojector.h" #include "projections/equirectangularprojector.h" #include "projections/lambertprojector.h" #include "projections/gnomonicprojector.h" #include "projections/orthographicprojector.h" #include "projections/stereographicprojector.h" #include #include #include #include #include #include #include #ifdef HAVE_XPLANET #include #include #endif #include namespace { // Draw bitmap for zoom cursor. Width is size of pen to draw with. QBitmap zoomCursorBitmap(int width) { QBitmap b(32, 32); b.fill(Qt::color0); int mx = 16, my = 16; // Begin drawing QPainter p; p.begin(&b); p.setPen(QPen(Qt::color1, width)); p.drawEllipse(mx - 7, my - 7, 14, 14); p.drawLine(mx + 5, my + 5, mx + 11, my + 11); p.end(); return b; } // Draw bitmap for default cursor. Width is size of pen to draw with. QBitmap defaultCursorBitmap(int width) { QBitmap b(32, 32); b.fill(Qt::color0); int mx = 16, my = 16; // Begin drawing QPainter p; p.begin(&b); p.setPen(QPen(Qt::color1, width)); // 1. diagonal p.drawLine(mx - 2, my - 2, mx - 8, mx - 8); p.drawLine(mx + 2, my + 2, mx + 8, mx + 8); // 2. diagonal p.drawLine(mx - 2, my + 2, mx - 8, mx + 8); p.drawLine(mx + 2, my - 2, mx + 8, mx - 8); p.end(); return b; } } -SkyMap *SkyMap::pinstance = 0; +SkyMap *SkyMap::pinstance = nullptr; SkyMap *SkyMap::Create() { delete pinstance; pinstance = new SkyMap(); return pinstance; } SkyMap *SkyMap::Instance() { return pinstance; } SkyMap::SkyMap() - : QGraphicsView(KStars::Instance()), computeSkymap(true), rulerMode(false), data(KStarsData::Instance()), pmenu(0), - ClickedObject(0), FocusObject(0), m_proj(0), m_previewLegend(false), m_objPointingMode(false) + : QGraphicsView(KStars::Instance()), computeSkymap(true), rulerMode(false), data(KStarsData::Instance()), pmenu(nullptr), + ClickedObject(nullptr), FocusObject(nullptr), m_proj(nullptr), m_previewLegend(false), m_objPointingMode(false) { m_Scale = 1.0; ZoomRect = QRect(); setDefaultMouseCursor(); // set the cross cursor QPalette p = palette(); p.setColor(QPalette::Window, QColor(data->colorScheme()->colorNamed("SkyColor"))); setPalette(p); setFocusPolicy(Qt::StrongFocus); setMinimumSize(380, 250); setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setStyleSheet("QGraphicsView { border-style: none; }"); setMouseTracking(true); //Generate MouseMove events! midMouseButtonDown = false; mouseButtonDown = false; slewing = false; clockSlewing = false; ClickedObject = nullptr; FocusObject = nullptr; - m_SkyMapDraw = 0; + m_SkyMapDraw = nullptr; pmenu = new KSPopupMenu(); setupProjector(); //Initialize Transient label stuff m_HoverTimer.setSingleShot(true); // using this timer as a single shot timer connect(&m_HoverTimer, SIGNAL(timeout()), this, SLOT(slotTransientLabel())); connect(this, SIGNAL(destinationChanged()), this, SLOT(slewFocus())); connect(KStarsData::Instance(), SIGNAL(skyUpdate(bool)), this, SLOT(slotUpdateSky(bool))); // Time infobox m_timeBox = new InfoBoxWidget(Options::shadeTimeBox(), Options::positionTimeBox(), Options::stickyTimeBox(), QStringList(), this); m_timeBox->setVisible(Options::showTimeBox()); connect(data->clock(), SIGNAL(timeChanged()), m_timeBox, SLOT(slotTimeChanged())); connect(data->clock(), SIGNAL(timeAdvanced()), m_timeBox, SLOT(slotTimeChanged())); // Geo infobox m_geoBox = new InfoBoxWidget(Options::shadeGeoBox(), Options::positionGeoBox(), Options::stickyGeoBox(), QStringList(), this); m_geoBox->setVisible(Options::showGeoBox()); connect(data, SIGNAL(geoChanged()), m_geoBox, SLOT(slotGeoChanged())); // Object infobox m_objBox = new InfoBoxWidget(Options::shadeFocusBox(), Options::positionFocusBox(), Options::stickyFocusBox(), QStringList(), this); m_objBox->setVisible(Options::showFocusBox()); connect(this, SIGNAL(objectChanged(SkyObject*)), m_objBox, SLOT(slotObjectChanged(SkyObject*))); connect(this, SIGNAL(positionChanged(SkyPoint*)), m_objBox, SLOT(slotPointChanged(SkyPoint*))); m_SkyMapDraw = new SkyMapQDraw(this); m_SkyMapDraw->setMouseTracking(true); m_SkyMapDraw->setParent(this->viewport()); m_SkyMapDraw->show(); m_iboxes = new InfoBoxes(m_SkyMapDraw); m_iboxes->setVisible(Options::showInfoBoxes()); m_iboxes->addInfoBox(m_timeBox); m_iboxes->addInfoBox(m_geoBox); m_iboxes->addInfoBox(m_objBox); } void SkyMap::slotToggleGeoBox(bool flag) { m_geoBox->setVisible(flag); } void SkyMap::slotToggleFocusBox(bool flag) { m_objBox->setVisible(flag); } void SkyMap::slotToggleTimeBox(bool flag) { m_timeBox->setVisible(flag); } void SkyMap::slotToggleInfoboxes(bool flag) { m_iboxes->setVisible(flag); } SkyMap::~SkyMap() { /* == Save infoxes status into Options == */ Options::setShowInfoBoxes(m_iboxes->isVisibleTo(parentWidget())); // Time box Options::setPositionTimeBox(m_timeBox->pos()); Options::setShadeTimeBox(m_timeBox->shaded()); Options::setStickyTimeBox(m_timeBox->sticky()); Options::setShowTimeBox(m_timeBox->isVisibleTo(m_iboxes)); // Geo box Options::setPositionGeoBox(m_geoBox->pos()); Options::setShadeGeoBox(m_geoBox->shaded()); Options::setStickyGeoBox(m_geoBox->sticky()); Options::setShowGeoBox(m_geoBox->isVisibleTo(m_iboxes)); // Obj box Options::setPositionFocusBox(m_objBox->pos()); Options::setShadeFocusBox(m_objBox->shaded()); Options::setStickyFocusBox(m_objBox->sticky()); Options::setShowFocusBox(m_objBox->isVisibleTo(m_iboxes)); //store focus values in Options //If not tracking and using Alt/Az coords, stor the Alt/Az coordinates if (Options::useAltAz() && !Options::isTracking()) { Options::setFocusRA(focus()->az().Degrees()); Options::setFocusDec(focus()->alt().Degrees()); } else { Options::setFocusRA(focus()->ra().Hours()); Options::setFocusDec(focus()->dec().Degrees()); } #ifdef HAVE_OPENGL delete m_SkyMapGLDraw; delete m_SkyMapQDraw; m_SkyMapDraw = 0; // Just a formality #else delete m_SkyMapDraw; #endif delete pmenu; delete m_proj; - pinstance = 0; + pinstance = nullptr; } void SkyMap::showFocusCoords() { if (focusObject() && Options::isTracking()) emit objectChanged(focusObject()); else emit positionChanged(focus()); } void SkyMap::slotTransientLabel() { //This function is only called if the HoverTimer manages to timeout. //(HoverTimer is restarted with every mouseMoveEvent; so if it times //out, that means there was no mouse movement for HOVER_INTERVAL msec.) if (hasFocus() && !slewing && !(Options::useAltAz() && Options::showGround() && SkyPoint::refract(m_MousePoint.alt()).Degrees() < 0.0)) { double maxrad = 1000.0 / Options::zoomFactor(); SkyObject *so = data->skyComposite()->objectNearest(&m_MousePoint, maxrad); if (so && !isObjectLabeled(so)) { QToolTip::showText(QCursor::pos(), i18n("%1: %2m", so->translatedLongName(), QString::number(so->mag(), 'f', 1)), this); } } } //Slots void SkyMap::setClickedObject(SkyObject *o) { ClickedObject = o; } void SkyMap::setFocusObject(SkyObject *o) { FocusObject = o; if (FocusObject) Options::setFocusObject(FocusObject->name()); else Options::setFocusObject(i18n("nothing")); } void SkyMap::slotCenter() { KStars *kstars = KStars::Instance(); TrailObject *trailObj = dynamic_cast(focusObject()); setFocusPoint(clickedPoint()); if (Options::useAltAz()) { // JM 2016-09-12: Following call has problems when ra0/dec0 of an object are not valid for example // because they're solar system bodies. So it creates a lot of issues. It is disabled and centering // works correctly for all different body types as I tested. DeepSkyObject *dso = dynamic_cast(focusObject()); if (dso) focusPoint()->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false); focusPoint()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } else focusPoint()->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false); qDebug() << "Centering on " << focusPoint()->ra().toHMSString() << " " << focusPoint()->dec().toDMSString(); //clear the planet trail of old focusObject, if it was temporary if (trailObj && data->temporaryTrail) { trailObj->clearTrail(); data->temporaryTrail = false; } //If the requested object is below the opaque horizon, issue a warning message //(unless user is already pointed below the horizon) if (Options::useAltAz() && Options::showGround() && focus()->alt().Degrees() > -1.0 && focusPoint()->alt().Degrees() < -1.0) { QString caption = i18n("Requested Position Below Horizon"); QString message = i18n("The requested position is below the horizon.\nWould you like to go there anyway?"); if (KMessageBox::warningYesNo(this, message, caption, KGuiItem(i18n("Go Anyway")), KGuiItem(i18n("Keep Position")), "dag_focus_below_horiz") == KMessageBox::No) { setClickedObject(nullptr); setFocusObject(nullptr); Options::setIsTracking(false); return; } } //set FocusObject before slewing. Otherwise, KStarsData::updateTime() can reset //destination to previous object... setFocusObject(ClickedObject); Options::setIsTracking(true); if (kstars) { kstars->actionCollection() ->action("track_object") ->setIcon(QIcon::fromTheme("document-encrypt", QIcon(":/icons/breeze/default/document-encrypt.svg"))); kstars->actionCollection()->action("track_object")->setText(i18n("Stop &Tracking")); } //If focusObject is a SS body and doesn't already have a trail, set the temporaryTrail if (Options::useAutoTrail() && trailObj && trailObj->hasTrail()) { trailObj->addToTrail(); data->temporaryTrail = true; } //update the destination to the selected coordinates if (Options::useAltAz()) { setDestinationAltAz(focusPoint()->altRefracted(), focusPoint()->az()); } else { setDestination(*focusPoint()); } focusPoint()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); //display coordinates in statusBar emit mousePointChanged(focusPoint()); showFocusCoords(); //update FocusBox } void SkyMap::slotUpdateSky(bool now) { // Code moved from KStarsData::updateTime() //Update focus updateFocus(); if (now) QTimer::singleShot( 0, this, SLOT(forceUpdateNow())); // Why is it done this way rather than just calling forceUpdateNow()? -- asimha else forceUpdate(); } void SkyMap::slotDSS() { dms ra(0.0), dec(0.0); QString urlstring; //ra and dec must be the coordinates at J2000. If we clicked on an object, just use the object's ra0, dec0 coords //if we clicked on empty sky, we need to precess to J2000. if (clickedObject()) { urlstring = KSDssDownloader::getDSSURL(clickedObject()); } else { SkyPoint deprecessedPoint = clickedPoint()->deprecess(data->updateNum()); ra = deprecessedPoint.ra(); dec = deprecessedPoint.dec(); urlstring = KSDssDownloader::getDSSURL(ra, dec); // Use default size for non-objects } QUrl url(urlstring); KStars *kstars = KStars::Instance(); if (kstars) { new ImageViewer( url, i18n("Digitized Sky Survey image provided by the Space Telescope Science Institute [public domain]."), this); //iv->show(); } } void SkyMap::slotSDSS() { // TODO: Remove code duplication -- we have the same stuff // implemented in ObservingList::setCurrentImage() etc. in // tools/observinglist.cpp; must try to de-duplicate as much as // possible. QString URLprefix("http://casjobs.sdss.org/ImgCutoutDR6/getjpeg.aspx?"); QString URLsuffix("&scale=1.0&width=600&height=600&opt=GST&query=SR(10,20)"); dms ra(0.0), dec(0.0); QString RAString, DecString; //ra and dec must be the coordinates at J2000. If we clicked on an object, just use the object's ra0, dec0 coords //if we clicked on empty sky, we need to precess to J2000. if (clickedObject()) { ra = clickedObject()->ra0(); dec = clickedObject()->dec0(); } else { SkyPoint deprecessedPoint = clickedPoint()->deprecess(data->updateNum()); ra = deprecessedPoint.ra(); dec = deprecessedPoint.dec(); } RAString = RAString.sprintf("ra=%f", ra.Degrees()); DecString = DecString.sprintf("&dec=%f", dec.Degrees()); //concat all the segments into the kview command line: QUrl url(URLprefix + RAString + DecString + URLsuffix); KStars *kstars = KStars::Instance(); if (kstars) { new ImageViewer(url, i18n("Sloan Digital Sky Survey image provided by the Astrophysical Research Consortium [free " "for non-commercial use]."), this); //iv->show(); } } void SkyMap::slotEyepieceView() { KStars::Instance()->slotEyepieceView((clickedObject() ? clickedObject() : clickedPoint())); } void SkyMap::slotBeginAngularDistance() { beginRulerMode(false); } void SkyMap::slotBeginStarHop() { beginRulerMode(true); } void SkyMap::beginRulerMode(bool starHopRuler) { rulerMode = true; starHopDefineMode = starHopRuler; AngularRuler.clear(); //If the cursor is near a SkyObject, reset the AngularRuler's //start point to the position of the SkyObject double maxrad = 1000.0 / Options::zoomFactor(); SkyObject *so = data->skyComposite()->objectNearest(clickedPoint(), maxrad); if (so) { AngularRuler.append(so); AngularRuler.append(so); m_rulerStartPoint = so; } else { AngularRuler.append(clickedPoint()); AngularRuler.append(clickedPoint()); m_rulerStartPoint = clickedPoint(); } AngularRuler.update(data); } void SkyMap::slotEndRulerMode() { if (!rulerMode) return; if (!starHopDefineMode) // Angular Ruler { QString sbMessage; //If the cursor is near a SkyObject, reset the AngularRuler's //end point to the position of the SkyObject double maxrad = 1000.0 / Options::zoomFactor(); SkyPoint *rulerEndPoint; SkyObject *so = data->skyComposite()->objectNearest(clickedPoint(), maxrad); if (so) { AngularRuler.setPoint(1, so); sbMessage = so->translatedLongName() + " "; rulerEndPoint = so; } else { AngularRuler.setPoint(1, clickedPoint()); rulerEndPoint = clickedPoint(); } rulerMode = false; AngularRuler.update(data); dms angularDistance = AngularRuler.angularSize(); sbMessage += i18n("Angular distance: %1", angularDistance.toDMSString()); const StarObject *p1 = dynamic_cast(m_rulerStartPoint); const StarObject *p2 = dynamic_cast(rulerEndPoint); qDebug() << "Starobjects? " << p1 << p2; if (p1 && p2) qDebug() << "Distances: " << p1->distance() << "pc; " << p2->distance() << "pc"; if (p1 && p2 && std::isfinite(p1->distance()) && std::isfinite(p2->distance()) && p1->distance() > 0 && p2->distance() > 0) { double dist = sqrt(p1->distance() * p1->distance() + p2->distance() * p2->distance() - 2 * p1->distance() * p2->distance() * cos(angularDistance.radians())); qDebug() << "Could calculate physical distance: " << dist << " pc"; sbMessage += i18n("; Physical distance: %1 pc", QString::number(dist)); } AngularRuler.clear(); // Create unobsructive message box with suicidal tendencies // to display result. InfoBoxWidget *box = new InfoBoxWidget(true, mapFromGlobal(QCursor::pos()), 0, QStringList(sbMessage), this); connect(box, SIGNAL(clicked()), box, SLOT(deleteLater())); QTimer::singleShot(5000, box, SLOT(deleteLater())); box->adjust(); box->show(); } else // Star Hop { StarHopperDialog *shd = new StarHopperDialog(this); const SkyPoint &startHop = *AngularRuler.point(0); const SkyPoint &stopHop = *clickedPoint(); double fov; // Field of view in arcminutes bool ok; // true if user did not cancel the operation if (data->getAvailableFOVs().size() == 1) { // Exactly 1 FOV symbol visible, so use that. Also assume a circular FOV of size min{sizeX, sizeY} FOV *f = data->getAvailableFOVs().first(); fov = ((f->sizeX() >= f->sizeY() && f->sizeY() != 0) ? f->sizeY() : f->sizeX()); ok = true; } else if (!data->getAvailableFOVs().isEmpty()) { // Ask the user to choose from a list of available FOVs. FOV const *f; QMap nameToFovMap; foreach (f, data->getAvailableFOVs()) { nameToFovMap.insert(f->name(), ((f->sizeX() >= f->sizeY() && f->sizeY() != 0) ? f->sizeY() : f->sizeX())); } fov = nameToFovMap[QInputDialog::getItem(this, i18n("Star Hopper: Choose a field-of-view"), i18n("FOV to use for star hopping:"), nameToFovMap.uniqueKeys(), 0, false, &ok)]; } else { // Ask the user to enter a field of view fov = QInputDialog::getDouble(this, i18n("Star Hopper: Enter field-of-view to use"), i18n("FOV to use for star hopping (in arcminutes):"), 60.0, 1.0, 600.0, 1, &ok); } Q_ASSERT(fov > 0.0); if (ok) { qDebug() << "fov = " << fov; shd->starHop(startHop, stopHop, fov / 60.0, 9.0); //FIXME: Hardcoded maglimit value shd->show(); } rulerMode = false; } } void SkyMap::slotCancelRulerMode(void) { rulerMode = false; AngularRuler.clear(); } void SkyMap::slotAddFlag() { KStars *ks = KStars::Instance(); // popup FlagManager window and update coordinates ks->slotFlagManager(); ks->flagManager()->clearFields(); //ra and dec must be the coordinates at J2000. If we clicked on an object, just use the object's ra0, dec0 coords //if we clicked on empty sky, we need to precess to J2000. dms J2000RA, J2000DE; if (clickedObject()) { J2000RA = clickedObject()->ra0(); J2000DE = clickedObject()->dec0(); } else { SkyPoint deprecessedPoint = clickedPoint()->deprecess(data->updateNum()); J2000RA = deprecessedPoint.ra(); J2000DE = deprecessedPoint.dec(); } ks->flagManager()->setRaDec(J2000RA, J2000DE); } void SkyMap::slotEditFlag(int flagIdx) { KStars *ks = KStars::Instance(); // popup FlagManager window and switch to selected flag ks->slotFlagManager(); ks->flagManager()->showFlag(flagIdx); } void SkyMap::slotDeleteFlag(int flagIdx) { KStars *ks = KStars::Instance(); ks->data()->skyComposite()->flags()->remove(flagIdx); ks->data()->skyComposite()->flags()->saveToFile(); // if there is FlagManager created, update its flag model if (ks->flagManager()) { ks->flagManager()->deleteFlagItem(flagIdx); } } void SkyMap::slotImage() { QString message = ((QAction *)sender())->text(); message = message.remove('&'); //Get rid of accelerator markers // Need to do this because we are comparing translated strings int index = -1; for (int i = 0; i < clickedObject()->ImageTitle().size(); ++i) { if (i18nc("Image/info menu item (should be translated)", clickedObject()->ImageTitle().at(i).toLocal8Bit().data()) == message) { index = i; break; } } QString sURL; if (index >= 0 && index < clickedObject()->ImageList().size()) { sURL = clickedObject()->ImageList()[index]; } else { qWarning() << "ImageList index out of bounds: " << index; if (index == -1) { qWarning() << "Message string \"" << message << "\" not found in ImageTitle."; qDebug() << clickedObject()->ImageTitle(); } } QUrl url(sURL); if (!url.isEmpty()) new ImageViewer(url, clickedObject()->messageFromTitle(message), this); } void SkyMap::slotInfo() { QString message = ((QAction *)sender())->text(); message = message.remove('&'); //Get rid of accelerator markers // Need to do this because we are comparing translated strings int index = -1; for (int i = 0; i < clickedObject()->InfoTitle().size(); ++i) { if (i18nc("Image/info menu item (should be translated)", clickedObject()->InfoTitle().at(i).toLocal8Bit().data()) == message) { index = i; break; } } QString sURL; if (index >= 0 && index < clickedObject()->InfoList().size()) { sURL = clickedObject()->InfoList()[index]; } else { qWarning() << "InfoList index out of bounds: " << index; if (index == -1) { qWarning() << "Message string \"" << message << "\" not found in InfoTitle."; qDebug() << clickedObject()->InfoTitle(); } } QUrl url(sURL); if (!url.isEmpty()) QDesktopServices::openUrl(url); } bool SkyMap::isObjectLabeled(SkyObject *object) { return data->skyComposite()->labelObjects().contains(object); } SkyPoint SkyMap::getCenterPoint() { SkyPoint retVal; // FIXME: subtraction of these 0.00001 is a simple workaround, because wrong // SkyPoint is returned when _exact_ center of SkyMap is passed to the projector. retVal = projector()->fromScreen(QPointF((qreal)width() / 2 - 0.00001, (qreal)height() / 2 - 0.00001), data->lst(), data->geo()->lat()); return retVal; } void SkyMap::slotRemoveObjectLabel() { data->skyComposite()->removeNameLabel(clickedObject()); forceUpdate(); } void SkyMap::slotAddObjectLabel() { data->skyComposite()->addNameLabel(clickedObject()); forceUpdate(); } void SkyMap::slotRemovePlanetTrail() { TrailObject *tobj = dynamic_cast(clickedObject()); if (tobj) { tobj->clearTrail(); forceUpdate(); } } void SkyMap::slotAddPlanetTrail() { TrailObject *tobj = dynamic_cast(clickedObject()); if (tobj) { tobj->addToTrail(); forceUpdate(); } } void SkyMap::slotDetail() { // check if object is selected if (!clickedObject()) { KMessageBox::sorry(this, i18n("No object selected."), i18n("Object Details")); return; } DetailDialog *detail = new DetailDialog(clickedObject(), data->ut(), data->geo(), KStars::Instance()); detail->setAttribute(Qt::WA_DeleteOnClose); detail->show(); } void SkyMap::slotObjectSelected() { if (m_objPointingMode && KStars::Instance()->printingWizard()) { KStars::Instance()->printingWizard()->pointingDone(clickedObject()); m_objPointingMode = false; } } void SkyMap::slotCancelLegendPreviewMode() { m_previewLegend = false; forceUpdate(true); KStars::Instance()->showImgExportDialog(); } void SkyMap::slotFinishFovCaptureMode() { if (m_fovCaptureMode && KStars::Instance()->printingWizard()) { KStars::Instance()->printingWizard()->fovCaptureDone(); m_fovCaptureMode = false; } } void SkyMap::slotCaptureFov() { if (KStars::Instance()->printingWizard()) { KStars::Instance()->printingWizard()->captureFov(); } } void SkyMap::slotClockSlewing() { //If the current timescale exceeds slewTimeScale, set clockSlewing=true, and stop the clock. if ((fabs(data->clock()->scale()) > Options::slewTimeScale()) ^ clockSlewing) { data->clock()->setManualMode(!clockSlewing); clockSlewing = !clockSlewing; // don't change automatically the DST status KStars *kstars = KStars::Instance(); if (kstars) kstars->updateTime(false); } } void SkyMap::setFocus(SkyPoint *p) { setFocus(p->ra(), p->dec()); } void SkyMap::setFocus(const dms &ra, const dms &dec) { Options::setFocusRA(ra.Hours()); Options::setFocusDec(dec.Degrees()); focus()->set(ra, dec); focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } void SkyMap::setFocusAltAz(const dms &alt, const dms &az) { Options::setFocusRA(focus()->ra().Hours()); Options::setFocusDec(focus()->dec().Degrees()); focus()->setAlt(alt); focus()->setAz(az); focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); slewing = false; forceUpdate(); //need a total update, or slewing with the arrow keys doesn't work. } void SkyMap::setDestination(const SkyPoint &p) { setDestination(p.ra(), p.dec()); } void SkyMap::setDestination(const dms &ra, const dms &dec) { destination()->set(ra, dec); destination()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); emit destinationChanged(); } void SkyMap::setDestinationAltAz(const dms &alt, const dms &az) { destination()->setAlt(alt); destination()->setAz(az); destination()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); emit destinationChanged(); } void SkyMap::setClickedPoint(SkyPoint *f) { ClickedPoint = *f; } void SkyMap::updateFocus() { if (slewing) return; //Tracking on an object if (Options::isTracking() && focusObject() != nullptr) { if (Options::useAltAz()) { //Tracking any object in Alt/Az mode requires focus updates focusObject()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); setFocusAltAz(focusObject()->altRefracted(), focusObject()->az()); focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); setDestination(*focus()); } else { //Tracking in equatorial coords setFocus(focusObject()); focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); setDestination(*focus()); } //Tracking on empty sky } else if (Options::isTracking() && focusPoint() != nullptr) { if (Options::useAltAz()) { //Tracking on empty sky in Alt/Az mode setFocus(focusPoint()); focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); setDestination(*focus()); } // Not tracking and not slewing, let sky drift by // This means that horizontal coordinates are constant. } else { focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); } } void SkyMap::slewFocus() { //Don't slew if the mouse button is pressed //Also, no animated slews if the Manual Clock is active //08/2002: added possibility for one-time skipping of slew with snapNextFocus if (!mouseButtonDown) { bool goSlew = (Options::useAnimatedSlewing() && !data->snapNextFocus()) && !(data->clock()->isManualMode() && data->clock()->isActive()); if (goSlew) { double dX, dY; double maxstep = 10.0; if (Options::useAltAz()) { dX = destination()->az().Degrees() - focus()->az().Degrees(); dY = destination()->alt().Degrees() - focus()->alt().Degrees(); } else { dX = destination()->ra().Degrees() - focus()->ra().Degrees(); dY = destination()->dec().Degrees() - focus()->dec().Degrees(); } //switch directions to go the short way around the celestial sphere, if necessary. dX = KSUtils::reduceAngle(dX, -180.0, 180.0); double r0 = sqrt(dX * dX + dY * dY); if (r0 < 20.0) //smaller slews have smaller maxstep { maxstep *= (10.0 + 0.5 * r0) / 20.0; } double step = 0.5; double r = r0; while (r > step) { //DEBUG //qDebug() << step << ": " << r << ": " << r0 << endl; double fX = dX / r; double fY = dY / r; if (Options::useAltAz()) { focus()->setAlt(focus()->alt().Degrees() + fY * step); focus()->setAz(dms(focus()->az().Degrees() + fX * step).reduce()); focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); } else { fX = fX / 15.; //convert RA degrees to hours SkyPoint newFocus(focus()->ra().Hours() + fX * step, focus()->dec().Degrees() + fY * step); setFocus(&newFocus); focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } slewing = true; forceUpdate(); qApp->processEvents(); //keep up with other stuff if (Options::useAltAz()) { dX = destination()->az().Degrees() - focus()->az().Degrees(); dY = destination()->alt().Degrees() - focus()->alt().Degrees(); } else { dX = destination()->ra().Degrees() - focus()->ra().Degrees(); dY = destination()->dec().Degrees() - focus()->dec().Degrees(); } //switch directions to go the short way around the celestial sphere, if necessary. dX = KSUtils::reduceAngle(dX, -180.0, 180.0); r = sqrt(dX * dX + dY * dY); //Modify step according to a cosine-shaped profile //centered on the midpoint of the slew //NOTE: don't allow the full range from -PI/2 to PI/2 //because the slew will never reach the destination as //the speed approaches zero at the end! double t = dms::PI * (r - 0.5 * r0) / (1.05 * r0); step = cos(t) * maxstep; } } //Either useAnimatedSlewing==false, or we have slewed, and are within one step of destination //set focus=destination. if (Options::useAltAz()) { setFocusAltAz(destination()->alt(), destination()->az()); focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); } else { setFocus(destination()); focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } slewing = false; //Turn off snapNextFocus, we only want it to happen once if (data->snapNextFocus()) { data->setSnapNextFocus(false); } //Start the HoverTimer. if the user leaves the mouse in place after a slew, //we want to attach a label to the nearest object. if (Options::useHoverLabel()) m_HoverTimer.start(HOVER_INTERVAL); forceUpdate(); } } void SkyMap::slotZoomIn() { setZoomFactor(Options::zoomFactor() * DZOOM); } void SkyMap::slotZoomOut() { setZoomFactor(Options::zoomFactor() / DZOOM); } void SkyMap::slotZoomDefault() { setZoomFactor(DEFAULTZOOM); } void SkyMap::setZoomFactor(double factor) { Options::setZoomFactor(KSUtils::clamp(factor, MINZOOM, MAXZOOM)); forceUpdate(); emit zoomChanged(); } // force a new calculation of the skymap (used instead of update(), which may skip the redraw) // if now=true, SkyMap::paintEvent() is run immediately, rather than being added to the event queue // also, determine new coordinates of mouse cursor. void SkyMap::forceUpdate(bool now) { QPoint mp(mapFromGlobal(QCursor::pos())); if (!projector()->unusablePoint(mp)) { //determine RA, Dec of mouse pointer m_MousePoint = projector()->fromScreen(mp, data->lst(), data->geo()->lat()); } computeSkymap = true; // Ensure that stars are recomputed data->incUpdateID(); if (now) m_SkyMapDraw->repaint(); else m_SkyMapDraw->update(); } float SkyMap::fov() { float diagonalPixels = sqrt(static_cast(width() * width() + height() * height())); return diagonalPixels / (2 * Options::zoomFactor() * dms::DegToRad); } void SkyMap::setupProjector() { //Update View Parameters for projection ViewParams p; p.focus = focus(); p.height = height(); p.width = width(); p.useAltAz = Options::useAltAz(); p.useRefraction = Options::useRefraction(); p.zoomFactor = Options::zoomFactor(); p.fillGround = Options::showGround(); //Check if we need a new projector if (m_proj && Options::projection() == m_proj->type()) m_proj->setViewParams(p); else { delete m_proj; switch (Options::projection()) { case Gnomonic: m_proj = new GnomonicProjector(p); break; case Stereographic: m_proj = new StereographicProjector(p); break; case Orthographic: m_proj = new OrthographicProjector(p); break; case AzimuthalEquidistant: m_proj = new AzimuthalEquidistantProjector(p); break; case Equirectangular: m_proj = new EquirectangularProjector(p); break; case Lambert: default: //TODO: implement other projection classes m_proj = new LambertProjector(p); break; } } } void SkyMap::setZoomMouseCursor() { mouseMoveCursor = false; // no mousemove cursor QBitmap cursor = zoomCursorBitmap(2); QBitmap mask = zoomCursorBitmap(4); setCursor(QCursor(cursor, mask)); } void SkyMap::setDefaultMouseCursor() { mouseMoveCursor = false; // no mousemove cursor QBitmap cursor = defaultCursorBitmap(2); QBitmap mask = defaultCursorBitmap(3); setCursor(QCursor(cursor, mask)); } void SkyMap::setMouseMoveCursor() { if (mouseButtonDown) { setCursor(Qt::SizeAllCursor); // cursor shape defined in qt mouseMoveCursor = true; } } void SkyMap::updateAngleRuler() { if (rulerMode && (!pmenu || !pmenu->isVisible())) AngularRuler.setPoint(1, &m_MousePoint); AngularRuler.update(data); } bool SkyMap::isSlewing() const { return (slewing || (clockSlewing && data->clock()->isActive())); } #ifdef HAVE_XPLANET void SkyMap::startXplanet(const QString &outputFile) { QString year, month, day, hour, minute, seconde, fov; // If Options::xplanetPath() is empty, return if (Options::xplanetPath().isEmpty()) { - KMessageBox::error(0, i18n("Xplanet binary path is empty in config panel.")); + KMessageBox::error(nullptr, i18n("Xplanet binary path is empty in config panel.")); return; } // Format date if (year.setNum(data->ut().date().year()).size() == 1) year.push_front('0'); if (month.setNum(data->ut().date().month()).size() == 1) month.push_front('0'); if (day.setNum(data->ut().date().day()).size() == 1) day.push_front('0'); if (hour.setNum(data->ut().time().hour()).size() == 1) hour.push_front('0'); if (minute.setNum(data->ut().time().minute()).size() == 1) minute.push_front('0'); if (seconde.setNum(data->ut().time().second()).size() == 1) seconde.push_front('0'); // Create xplanet process QProcess *xplanetProc = new QProcess; // Add some options QStringList args; args << "-body" << clickedObject()->name().toLower() << "-geometry" << Options::xplanetWidth() + 'x' + Options::xplanetHeight() << "-date" << year + month + day + '.' + hour + minute + seconde << "-glare" << Options::xplanetGlare() << "-base_magnitude" << Options::xplanetMagnitude() << "-light_time" << "-window"; // General options if (!Options::xplanetTitle().isEmpty()) args << "-window_title" << "\"" + Options::xplanetTitle() + "\""; if (Options::xplanetFOV()) args << "-fov" << fov.setNum(this->fov()).replace('.', ','); if (Options::xplanetConfigFile()) args << "-config" << Options::xplanetConfigFilePath(); if (Options::xplanetStarmap()) args << "-starmap" << Options::xplanetStarmapPath(); if (Options::xplanetArcFile()) args << "-arc_file" << Options::xplanetArcFilePath(); if (Options::xplanetWait()) args << "-wait" << Options::xplanetWaitValue(); if (!outputFile.isEmpty()) args << "-output" << outputFile << "-quality" << Options::xplanetQuality(); // Labels if (Options::xplanetLabel()) { args << "-fontsize" << Options::xplanetFontSize() << "-color" << "0x" + Options::xplanetColor().mid(1) << "-date_format" << Options::xplanetDateFormat(); if (Options::xplanetLabelGMT()) args << "-gmtlabel"; else args << "-label"; if (!Options::xplanetLabelString().isEmpty()) args << "-label_string" << "\"" + Options::xplanetLabelString() + "\""; if (Options::xplanetLabelTL()) args << "-labelpos" << "+15+15"; else if (Options::xplanetLabelTR()) args << "-labelpos" << "-15+15"; else if (Options::xplanetLabelBR()) args << "-labelpos" << "-15-15"; else if (Options::xplanetLabelBL()) args << "-labelpos" << "+15-15"; } // Markers if (Options::xplanetMarkerFile()) args << "-marker_file" << Options::xplanetMarkerFilePath(); if (Options::xplanetMarkerBounds()) args << "-markerbounds" << Options::xplanetMarkerBoundsPath(); // Position if (Options::xplanetRandom()) args << "-random"; else args << "-latitude" << Options::xplanetLatitude() << "-longitude" << Options::xplanetLongitude(); // Projection if (Options::xplanetProjection()) { switch (Options::xplanetProjection()) { case 1: args << "-projection" << "ancient"; break; case 2: args << "-projection" << "azimuthal"; break; case 3: args << "-projection" << "bonne"; break; case 4: args << "-projection" << "gnomonic"; break; case 5: args << "-projection" << "hemisphere"; break; case 6: args << "-projection" << "lambert"; break; case 7: args << "-projection" << "mercator"; break; case 8: args << "-projection" << "mollweide"; break; case 9: args << "-projection" << "orthographic"; break; case 10: args << "-projection" << "peters"; break; case 11: args << "-projection" << "polyconic"; break; case 12: args << "-projection" << "rectangular"; break; case 13: args << "-projection" << "tsc"; break; default: break; } if (Options::xplanetBackground()) { if (Options::xplanetBackgroundImage()) args << "-background" << Options::xplanetBackgroundImagePath(); else args << "-background" << "0x" + Options::xplanetBackgroundColorValue().mid(1); } } // We add this option at the end otherwise it does not work (???) args << "-origin" << "earth"; // Run xplanet //qDebug() << "Run:" << xplanetProc->program().join(" "); QString xPlanetLocation = Options::xplanetPath(); #ifdef Q_OS_OSX if (Options::xplanetIsInternal()) { xPlanetLocation = QCoreApplication::applicationDirPath() + "/xplanet/bin/xplanet"; QString searchDir = QCoreApplication::applicationDirPath() + "/xplanet/share/xplanet/"; args << "-searchdir" << searchDir; } #endif xplanetProc->start(xPlanetLocation, args); if (xplanetProc) { xplanetProc->waitForFinished(1000); new ImageViewer(QUrl::fromLocalFile(outputFile), "XPlanet View: " + clickedObject()->name() + ", " + data->lt().date().toString() + ", " + data->lt().time().toString(), this); //iv->show(); } else { KMessageBox::sorry(this, i18n("XPlanet Program Error")); } } void SkyMap::slotXplanetToWindow() { QDir writableDir; QString xPlanetDirPath = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "xplanet"; writableDir.mkpath(xPlanetDirPath); QString xPlanetPath = xPlanetDirPath + QDir::separator() + clickedObject()->name() + ".png"; startXplanet(xPlanetPath); } void SkyMap::slotXplanetToFile() { QString filename = QFileDialog::getSaveFileName(); if (!filename.isEmpty()) { startXplanet(filename); } } #endif diff --git a/kstars/skymapevents.cpp b/kstars/skymapevents.cpp index 66d7096d7..e7b4417d8 100644 --- a/kstars/skymapevents.cpp +++ b/kstars/skymapevents.cpp @@ -1,742 +1,742 @@ /*************************************************************************** skymapevents.cpp - K Desktop Planetarium ------------------- begin : Sat Feb 10 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. * * * ***************************************************************************/ //This file contains Event handlers for the SkyMap class. #include "skymap.h" #include "ksplanetbase.h" #include "kspopupmenu.h" #include "kstars.h" #include "observinglist.h" #include "Options.h" #include "skyglpainter.h" #include "skyqpainter.h" #include "printing/simplefovexporter.h" #include "skycomponents/skylabeler.h" #include "skycomponents/skymapcomposite.h" #include "skycomponents/starcomponent.h" #include "widgets/infoboxwidget.h" #include #include void SkyMap::resizeEvent(QResizeEvent *) { computeSkymap = true; // skymap must be new computed //FIXME: No equivalent for this line in Qt4 ?? // if ( testWState( Qt::WState_AutoMask ) ) updateMask(); // Resize the widget that draws the sky map. // FIXME: The resize event doesn't pass to children. Any better way of doing this? m_SkyMapDraw->resize(size()); // Resize infoboxes container. // FIXME: this is not really pretty. Maybe there are some better way to this??? m_iboxes->resize(size()); } void SkyMap::keyPressEvent(QKeyEvent *e) { bool arrowKeyPressed(false); bool shiftPressed(false); float step = 1.0; if (e->modifiers() & Qt::ShiftModifier) { step = 10.0; shiftPressed = true; } //If the DBus resume key is not empty, then DBus processing is //paused while we wait for a keypress if (!data->resumeKey.isEmpty() && QKeySequence(e->key()) == data->resumeKey) { //The resumeKey was pressed. Signal that it was pressed by //resetting it to empty; this will break the loop in //KStars::waitForKey() data->resumeKey = QKeySequence(); return; } if (m_previewLegend) { slotCancelLegendPreviewMode(); } switch (e->key()) { case Qt::Key_Left: if (Options::useAltAz()) { focus()->setAz(dms(focus()->az().Degrees() - step * MINZOOM / Options::zoomFactor()).reduce()); focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); } else { focus()->setRA(focus()->ra().Hours() + 0.05 * step * MINZOOM / Options::zoomFactor()); focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } arrowKeyPressed = true; slewing = true; break; case Qt::Key_Right: if (Options::useAltAz()) { focus()->setAz(dms(focus()->az().Degrees() + step * MINZOOM / Options::zoomFactor()).reduce()); focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); } else { focus()->setRA(focus()->ra().Hours() - 0.05 * step * MINZOOM / Options::zoomFactor()); focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } arrowKeyPressed = true; slewing = true; break; case Qt::Key_Up: if (Options::useAltAz()) { focus()->setAlt(focus()->alt().Degrees() + step * MINZOOM / Options::zoomFactor()); if (focus()->alt().Degrees() > 90.0) focus()->setAlt(90.0); focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); } else { focus()->setDec(focus()->dec().Degrees() + step * MINZOOM / Options::zoomFactor()); if (focus()->dec().Degrees() > 90.0) focus()->setDec(90.0); focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } arrowKeyPressed = true; slewing = true; break; case Qt::Key_Down: if (Options::useAltAz()) { focus()->setAlt(focus()->alt().Degrees() - step * MINZOOM / Options::zoomFactor()); if (focus()->alt().Degrees() < -90.0) focus()->setAlt(-90.0); focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); } else { focus()->setDec(focus()->dec().Degrees() - step * MINZOOM / Options::zoomFactor()); if (focus()->dec().Degrees() < -90.0) focus()->setDec(-90.0); focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } arrowKeyPressed = true; slewing = true; break; case Qt::Key_Plus: //Zoom in case Qt::Key_Equal: zoomInOrMagStep(e->modifiers()); break; case Qt::Key_Minus: //Zoom out case Qt::Key_Underscore: zoomOutOrMagStep(e->modifiers()); break; case Qt::Key_0: //center on Sun setClickedObject(data->skyComposite()->planet(KSPlanetBase::SUN)); setClickedPoint(clickedObject()); slotCenter(); break; case Qt::Key_1: //center on Mercury setClickedObject(data->skyComposite()->planet(KSPlanetBase::MERCURY)); setClickedPoint(clickedObject()); slotCenter(); break; case Qt::Key_2: //center on Venus setClickedObject(data->skyComposite()->planet(KSPlanetBase::VENUS)); setClickedPoint(clickedObject()); slotCenter(); break; case Qt::Key_3: //center on Moon setClickedObject(data->skyComposite()->planet(KSPlanetBase::MOON)); setClickedPoint(clickedObject()); slotCenter(); break; case Qt::Key_4: //center on Mars setClickedObject(data->skyComposite()->planet(KSPlanetBase::MARS)); setClickedPoint(clickedObject()); slotCenter(); break; case Qt::Key_5: //center on Jupiter setClickedObject(data->skyComposite()->planet(KSPlanetBase::JUPITER)); setClickedPoint(clickedObject()); slotCenter(); break; case Qt::Key_6: //center on Saturn setClickedObject(data->skyComposite()->planet(KSPlanetBase::SATURN)); setClickedPoint(clickedObject()); slotCenter(); break; case Qt::Key_7: //center on Uranus setClickedObject(data->skyComposite()->planet(KSPlanetBase::URANUS)); setClickedPoint(clickedObject()); slotCenter(); break; case Qt::Key_8: //center on Neptune setClickedObject(data->skyComposite()->planet(KSPlanetBase::NEPTUNE)); setClickedPoint(clickedObject()); slotCenter(); break; /*case Qt::Key_9: //center on Pluto setClickedObject( data->skyComposite()->planet( KSPlanetBase::PLUTO ) ); setClickedPoint( clickedObject() ); slotCenter(); break;*/ case Qt::Key_BracketLeft: // Begin measuring angular distance if (!rulerMode) slotBeginAngularDistance(); break; case Qt::Key_Escape: // Cancel angular distance measurement { if (rulerMode) slotCancelRulerMode(); if (m_fovCaptureMode) slotFinishFovCaptureMode(); break; } case Qt::Key_C: //Center clicked object if (clickedObject()) slotCenter(); break; case Qt::Key_D: //Details window for Clicked/Centered object { - SkyObject *orig = 0; + SkyObject *orig = nullptr; if (shiftPressed) { orig = clickedObject(); setClickedObject(focusObject()); } if (clickedObject()) { slotDetail(); } if (orig) { setClickedObject(orig); } break; } case Qt::Key_P: //Show Popup menu for Clicked/Centered object if (shiftPressed) { if (focusObject()) focusObject()->showPopupMenu(pmenu, QCursor::pos()); } else { if (clickedObject()) clickedObject()->showPopupMenu(pmenu, QCursor::pos()); } break; case Qt::Key_O: //Add object to Observing List { - SkyObject *orig = 0; + SkyObject *orig = nullptr; if (shiftPressed) { orig = clickedObject(); setClickedObject(focusObject()); } if (clickedObject()) { data->observingList()->slotAddObject(); } if (orig) { setClickedObject(orig); } break; } case Qt::Key_L: //Toggle User label on Clicked/Centered object { - SkyObject *orig = 0; + SkyObject *orig = nullptr; if (shiftPressed) { orig = clickedObject(); setClickedObject(focusObject()); } if (clickedObject()) { if (isObjectLabeled(clickedObject())) slotRemoveObjectLabel(); else slotAddObjectLabel(); } if (orig) { setClickedObject(orig); } break; } case Qt::Key_T: //Toggle planet trail on Clicked/Centered object (if solsys) { - SkyObject *orig = 0; + SkyObject *orig = nullptr; if (shiftPressed) { orig = clickedObject(); setClickedObject(focusObject()); } KSPlanetBase *planet = dynamic_cast(clickedObject()); if (planet) { if (planet->hasTrail()) slotRemovePlanetTrail(); else slotAddPlanetTrail(); } if (orig) { setClickedObject(orig); } break; } case Qt::Key_R: { // Toggle relativistic corrections Options::setUseRelativistic(!Options::useRelativistic()); qDebug() << "Relativistc corrections: " << Options::useRelativistic(); forceUpdate(); break; } case Qt::Key_A: Options::setUseAntialias(!Options::useAntialias()); qDebug() << "Use Antialiasing: " << Options::useAntialias(); forceUpdate(); break; case Qt::Key_K: { if (m_fovCaptureMode) slotCaptureFov(); break; } case Qt::Key_PageUp: { KStars::Instance()->selectPreviousFov(); break; } case Qt::Key_PageDown: { KStars::Instance()->selectNextFov(); break; } default: // We don't want to do anything in this case. Key is unknown return; } if (arrowKeyPressed) { stopTracking(); setDestination(*focus()); } forceUpdate(); //need a total update, or slewing with the arrow keys doesn't work. } void SkyMap::stopTracking() { KStars *kstars = KStars::Instance(); emit positionChanged(focus()); if (kstars && Options::isTracking()) kstars->slotTrack(); } void SkyMap::keyReleaseEvent(QKeyEvent *e) { switch (e->key()) { case Qt::Key_Plus: //Zoom in case Qt::Key_Equal: case Qt::Key_Minus: //Zoom out case Qt::Key_Underscore: case Qt::Key_Left: //no break; continue to Qt::Key_Down case Qt::Key_Right: //no break; continue to Qt::Key_Down case Qt::Key_Up: //no break; continue to Qt::Key_Down case Qt::Key_Down: slewing = false; if (Options::useAltAz()) setDestinationAltAz(focus()->alt(), focus()->az()); else setDestination(*focus()); showFocusCoords(); forceUpdate(); // Need a full update to draw faint objects that are not drawn while slewing. break; } } void SkyMap::mouseMoveEvent(QMouseEvent *e) { if (Options::useHoverLabel()) { //Start a single-shot timer to monitor whether we are currently hovering. //The idea is that whenever a moveEvent occurs, the timer is reset. It //will only timeout if there are no move events for HOVER_INTERVAL ms m_HoverTimer.start(HOVER_INTERVAL); QToolTip::hideText(); } //Are we defining a ZoomRect? if (ZoomRect.center().x() > 0 && ZoomRect.center().y() > 0) { //cancel operation if the user let go of CTRL if (!(e->modifiers() & Qt::ControlModifier)) { ZoomRect = QRect(); //invalidate ZoomRect update(); } else { //Resize the rectangle so that it passes through the cursor position QPoint pcenter = ZoomRect.center(); int dx = abs(e->x() - pcenter.x()); int dy = abs(e->y() - pcenter.y()); if (dx == 0 || float(dy) / float(dx) > float(height()) / float(width())) { //Size rect by height ZoomRect.setHeight(2 * dy); ZoomRect.setWidth(2 * dy * width() / height()); } else { //Size rect by height ZoomRect.setWidth(2 * dx); ZoomRect.setHeight(2 * dx * height() / width()); } ZoomRect.moveCenter(pcenter); //reset center update(); return; } } if (projector()->unusablePoint(e->pos())) return; // break if point is unusable //determine RA, Dec of mouse pointer m_MousePoint = projector()->fromScreen(e->pos(), data->lst(), data->geo()->lat()); double dyPix = 0.5 * height() - e->y(); if (midMouseButtonDown) //zoom according to y-offset { float yoff = dyPix - y0; if (yoff > 10) { y0 = dyPix; slotZoomIn(); } if (yoff < -10) { y0 = dyPix; slotZoomOut(); } } if (mouseButtonDown) { // set the mouseMoveCursor and set slewing=true, if they are not set yet if (!mouseMoveCursor) setMouseMoveCursor(); if (!slewing) { slewing = true; stopTracking(); //toggle tracking off } //Update focus such that the sky coords at mouse cursor remain approximately constant if (Options::useAltAz()) { m_MousePoint.EquatorialToHorizontal(data->lst(), data->geo()->lat()); clickedPoint()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); dms dAz = m_MousePoint.az() - clickedPoint()->az(); dms dAlt = m_MousePoint.alt() - clickedPoint()->alt(); focus()->setAz(focus()->az().Degrees() - dAz.Degrees()); //move focus in opposite direction focus()->setAz(focus()->az().reduce()); focus()->setAlt(KSUtils::clamp(focus()->alt().Degrees() - dAlt.Degrees(), -90.0, 90.0)); focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat()); } else { dms dRA = m_MousePoint.ra() - clickedPoint()->ra(); dms dDec = m_MousePoint.dec() - clickedPoint()->dec(); focus()->setRA(focus()->ra().Hours() - dRA.Hours()); //move focus in opposite direction focus()->setRA(focus()->ra().reduce()); focus()->setDec(KSUtils::clamp(focus()->dec().Degrees() - dDec.Degrees(), -90.0, 90.0)); focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } showFocusCoords(); //redetermine RA, Dec of mouse pointer, using new focus m_MousePoint = projector()->fromScreen(e->pos(), data->lst(), data->geo()->lat()); setClickedPoint(&m_MousePoint); forceUpdate(); // must be new computed } else //mouse button not down { if (Options::useAltAz()) m_MousePoint.EquatorialToHorizontal(data->lst(), data->geo()->lat()); emit mousePointChanged(&m_MousePoint); } } void SkyMap::wheelEvent(QWheelEvent *e) { if (e->delta() > 0) zoomInOrMagStep(e->modifiers()); else if (e->delta() < 0) zoomOutOrMagStep(e->modifiers()); } void SkyMap::mouseReleaseEvent(QMouseEvent *) { if (ZoomRect.isValid()) { stopTracking(); SkyPoint newcenter = projector()->fromScreen(ZoomRect.center(), data->lst(), data->geo()->lat()); setFocus(&newcenter); setDestination(newcenter); //Zoom in on center of Zoom Circle, by a factor equal to the ratio //of the sky pixmap's width to the Zoom Circle's diameter float factor = float(width()) / float(ZoomRect.width()); setZoomFactor(Options::zoomFactor() * factor); } setDefaultMouseCursor(); ZoomRect = QRect(); //invalidate ZoomRect if (m_previewLegend) { slotCancelLegendPreviewMode(); } //false if double-clicked, because it's unset there. if (mouseButtonDown) { mouseButtonDown = false; if (slewing) { slewing = false; if (Options::useAltAz()) setDestinationAltAz(focus()->alt(), focus()->az()); else setDestination(*focus()); } forceUpdate(); // is needed because after moving the sky not all stars are shown } // if middle button was pressed unset here midMouseButtonDown = false; } void SkyMap::mousePressEvent(QMouseEvent *e) { KStars *kstars = KStars::Instance(); if ((e->modifiers() & Qt::ControlModifier) && (e->button() == Qt::LeftButton)) { ZoomRect.moveCenter(e->pos()); setZoomMouseCursor(); update(); //refresh without redrawing skymap return; } // if button is down and cursor is not moved set the move cursor after 500 ms QTimer::singleShot(500, this, SLOT(setMouseMoveCursor())); // break if point is unusable if (projector()->unusablePoint(e->pos())) return; if (!midMouseButtonDown && e->button() == Qt::MidButton) { y0 = 0.5 * height() - e->y(); //record y pixel coordinate for middle-button zooming midMouseButtonDown = true; } if (!mouseButtonDown) { if (e->button() == Qt::LeftButton) { mouseButtonDown = true; } //determine RA, Dec of mouse pointer m_MousePoint = projector()->fromScreen(e->pos(), data->lst(), data->geo()->lat()); setClickedPoint(&m_MousePoint); //Find object nearest to clickedPoint() double maxrad = 1000.0 / Options::zoomFactor(); SkyObject *obj = data->skyComposite()->objectNearest(clickedPoint(), maxrad); setClickedObject(obj); if (obj) setClickedPoint(obj); switch (e->button()) { case Qt::LeftButton: { QString name; if (clickedObject()) { name = clickedObject()->translatedLongName(); emit objectClicked(clickedObject()); } else name = i18n("Empty sky"); //kstars->statusBar()->changeItem(name, 0 ); kstars->statusBar()->showMessage(name, 0); emit positionClicked(&m_MousePoint); } break; case Qt::RightButton: if (rulerMode) { // Compute angular distance. slotEndRulerMode(); } else { // Show popup menu if (clickedObject()) { clickedObject()->showPopupMenu(pmenu, QCursor::pos()); } else { pmenu->createEmptyMenu(clickedPoint()); pmenu->popup(QCursor::pos()); } } break; default:; } } } void SkyMap::mouseDoubleClickEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton && !projector()->unusablePoint(e->pos())) { mouseButtonDown = false; if (e->x() != width() / 2 || e->y() != height() / 2) slotCenter(); } } double SkyMap::zoomFactor(const int modifier) { double factor = (modifier & Qt::ControlModifier) ? DZOOM : (Options::zoomScrollFactor() + 1); if (modifier & Qt::ShiftModifier) factor = sqrt(factor); return factor; } void SkyMap::zoomInOrMagStep(const int modifier) { if (modifier & Qt::AltModifier) incMagLimit(modifier); else setZoomFactor(Options::zoomFactor() * zoomFactor(modifier)); } void SkyMap::zoomOutOrMagStep(const int modifier) { if (modifier & Qt::AltModifier) decMagLimit(modifier); else setZoomFactor(Options::zoomFactor() / zoomFactor(modifier)); } double SkyMap::magFactor(const int modifier) { double factor = (modifier & Qt::ControlModifier) ? 0.1 : 0.5; if (modifier & Qt::ShiftModifier) factor *= 2.0; return factor; } void SkyMap::incMagLimit(const int modifier) { double limit = 2.222 * log10(static_cast(Options::starDensity())) + 0.35; limit += magFactor(modifier); if (limit > 5.75954) limit = 5.75954; Options::setStarDensity(pow(10, (limit - 0.35) / 2.222)); //printf("maglim set to %3.1f\n", limit); forceUpdate(); } void SkyMap::decMagLimit(const int modifier) { double limit = 2.222 * log10(static_cast(Options::starDensity())) + 0.35; limit -= magFactor(modifier); if (limit < 1.18778) limit = 1.18778; Options::setStarDensity(pow(10, (limit - 0.35) / 2.222)); //printf("maglim set to %3.1f\n", limit); forceUpdate(); } diff --git a/kstars/skyobjects/ksplanetbase.cpp b/kstars/skyobjects/ksplanetbase.cpp index 5fc3724b7..60dfa77a2 100644 --- a/kstars/skyobjects/ksplanetbase.cpp +++ b/kstars/skyobjects/ksplanetbase.cpp @@ -1,309 +1,309 @@ /*************************************************************************** ksplanetbase.cpp - K Desktop Planetarium ------------------- begin : Sun Jul 22 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 "ksplanetbase.h" #include "ksnumbers.h" #include "kstarsdata.h" #include "ksutils.h" #include "Options.h" #include "skymap.h" #include "ksasteroid.h" #include "kscomet.h" #include "ksmoon.h" #include "ksplanet.h" #include "kssun.h" #include "texturemanager.h" #include "skycomponents/skymapcomposite.h" QVector KSPlanetBase::planetColor = QVector() << QColor("slateblue") << //Mercury QColor("lightgreen") << //Venus QColor("red") << //Mars QColor("goldenrod") << //Jupiter QColor("khaki") << //Saturn QColor("lightseagreen") << //Uranus QColor("skyblue") << //Neptune QColor("grey") << //Pluto QColor("yellow") << //Sun QColor("white"); //Moon const SkyObject::UID KSPlanetBase::UID_SOL_BIGOBJ = 0; const SkyObject::UID KSPlanetBase::UID_SOL_ASTEROID = 1; const SkyObject::UID KSPlanetBase::UID_SOL_COMET = 2; KSPlanetBase::KSPlanetBase(const QString &s, const QString &image_file, const QColor &c, double pSize) : TrailObject(2, 0.0, 0.0, 0.0, s), Rearth(NaN::d) { init(s, image_file, c, pSize); } void KSPlanetBase::init(const QString &s, const QString &image_file, const QColor &c, double pSize) { m_image = TextureManager::getImage(image_file); PositionAngle = 0.0; PhysicalSize = pSize; m_Color = c; setName(s); setLongName(s); } KSPlanetBase *KSPlanetBase::createPlanet(int n) { switch (n) { case KSPlanetBase::MERCURY: case KSPlanetBase::VENUS: case KSPlanetBase::MARS: case KSPlanetBase::JUPITER: case KSPlanetBase::SATURN: case KSPlanetBase::URANUS: case KSPlanetBase::NEPTUNE: return new KSPlanet(n); break; /*case KSPlanetBase::PLUTO: return new KSPluto(); break;*/ case KSPlanetBase::SUN: return new KSSun(); break; case KSPlanetBase::MOON: return new KSMoon(); break; } - return 0; + return nullptr; } void KSPlanetBase::EquatorialToEcliptic(const CachingDms *Obliquity) { findEcliptic(Obliquity, ep.longitude, ep.latitude); } void KSPlanetBase::EclipticToEquatorial(const CachingDms *Obliquity) { setFromEcliptic(Obliquity, ep.longitude, ep.latitude); } void KSPlanetBase::updateCoords(const KSNumbers *num, bool includePlanets, const CachingDms *lat, const CachingDms *LST, bool) { KStarsData *kd = KStarsData::Instance(); if (kd == nullptr || !includePlanets) return; kd->skyComposite()->earth()->findPosition(num); //since we don't pass lat & LST, localizeCoords will be skipped if (lat && LST) { findPosition(num, lat, LST, kd->skyComposite()->earth()); // Don't add to the trail this time if (hasTrail()) Trail.takeLast(); } else { findGeocentricPosition(num, kd->skyComposite()->earth()); } } void KSPlanetBase::findPosition(const KSNumbers *num, const CachingDms *lat, const CachingDms *LST, const KSPlanetBase *Earth) { // DEBUG edit findGeocentricPosition(num, Earth); //private function, reimplemented in each subclass findPhase(); setAngularSize(asin(physicalSize() / Rearth / AU_KM) * 60. * 180. / dms::PI); //angular size in arcmin if (lat && LST) localizeCoords(num, lat, LST); //correct for figure-of-the-Earth if (hasTrail()) { addToTrail(KStarsDateTime(num->getJD()).toString("yyyy.MM.dd hh:mm") + i18nc("Universal time", "UT")); // TODO: Localize date/time format? if (Trail.size() > TrailObject::MaxTrail) clipTrail(); } findMagnitude(num); if (type() == SkyObject::COMET) { // Compute tail size KSComet *me = (KSComet *)this; double comaAngSize; // Convert the tail size in km to angular tail size (degrees) comaAngSize = asin(physicalSize() / Rearth / AU_KM) * 60.0 * 180.0 / dms::PI; // Find the apparent length as projected on the celestial sphere (the comet's tail points away from the sun) me->setComaAngSize(comaAngSize * fabs(sin(phase().radians()))); } } bool KSPlanetBase::isMajorPlanet() const { if (name() == i18n("Mercury") || name() == i18n("Venus") || name() == i18n("Mars") || name() == i18n("Jupiter") || name() == i18n("Saturn") || name() == i18n("Uranus") || name() == i18n("Neptune")) return true; return false; } void KSPlanetBase::localizeCoords(const KSNumbers *num, const CachingDms *lat, const CachingDms *LST) { //convert geocentric coordinates to local apparent coordinates (topocentric coordinates) dms HA, HA2; //Hour Angle, before and after correction double rsinp, rcosp, u, sinHA, cosHA, sinDec, cosDec, D; double cosHA2; double r = Rearth * AU_KM; //distance from Earth, in km u = atan(0.996647 * tan(lat->radians())); rsinp = 0.996647 * sin(u); rcosp = cos(u); HA.setD(LST->Degrees() - ra().Degrees()); HA.SinCos(sinHA, cosHA); dec().SinCos(sinDec, cosDec); D = atan2(rcosp * sinHA, r * cosDec / 6378.14 - rcosp * cosHA); dms temp; temp.setRadians(ra().radians() - D); setRA(temp); HA2.setD(LST->Degrees() - ra().Degrees()); cosHA2 = cos(HA2.radians()); //temp.setRadians( atan2( cosHA2*( r*sinDec/6378.14 - rsinp ), r*cosDec*cosHA/6378.14 - rcosp ) ); // The atan2() version above makes the planets move crazy in the htm branch -jbb temp.setRadians(atan(cosHA2 * (r * sinDec / 6378.14 - rsinp) / (r * cosDec * cosHA / 6378.14 - rcosp))); setDec(temp); //Make sure Dec is between -90 and +90 if (dec().Degrees() > 90.0) { setDec(180.0 - dec().Degrees()); setRA(ra().Hours() + 12.0); ra().reduce(); } if (dec().Degrees() < -90.0) { setDec(180.0 + dec().Degrees()); setRA(ra().Hours() + 12.0); ra().reduce(); } EquatorialToEcliptic(num->obliquity()); } void KSPlanetBase::setRearth(const KSPlanetBase *Earth) { double sinL, sinB, sinL0, sinB0; double cosL, cosB, cosL0, cosB0; double x, y, z; //The Moon's Rearth is set in its findGeocentricPosition()... if (name() == "Moon") { return; } if (name() == "Earth") { Rearth = 0.0; return; } if (!Earth) { qDebug() << "KSPlanetBase::setRearth(): Error: Need an Earth pointer. (" << name() << ")"; Rearth = 1.0; return; } Earth->ecLong().SinCos(sinL0, cosL0); Earth->ecLat().SinCos(sinB0, cosB0); double eX = Earth->rsun() * cosB0 * cosL0; double eY = Earth->rsun() * cosB0 * sinL0; double eZ = Earth->rsun() * sinB0; helEcLong().SinCos(sinL, cosL); helEcLat().SinCos(sinB, cosB); x = rsun() * cosB * cosL - eX; y = rsun() * cosB * sinL - eY; z = rsun() * sinB - eZ; Rearth = sqrt(x * x + y * y + z * z); //Set angular size, in arcmin AngularSize = asin(PhysicalSize / Rearth / AU_KM) * 60. * 180. / dms::PI; } void KSPlanetBase::findPA(const KSNumbers *num) { //Determine position angle of planet (assuming that it is aligned with //the Ecliptic, which is only roughly correct). //Displace a point along +Ecliptic Latitude by 1 degree SkyPoint test; dms newELat(ecLat().Degrees() + 1.0); test.setFromEcliptic(num->obliquity(), ecLong(), newELat); double dx = ra().Degrees() - test.ra().Degrees(); double dy = test.dec().Degrees() - dec().Degrees(); double pa; if (dy) { pa = atan2(dx, dy) * 180.0 / dms::PI; } else { pa = dx < 0 ? 90.0 : -90.0; } setPA(pa); } double KSPlanetBase::labelOffset() const { double size = angSize() * dms::PI * Options::zoomFactor() / 10800.0; //Determine minimum size for offset double minsize = 4.; if (type() == SkyObject::ASTEROID || type() == SkyObject::COMET) minsize = 2.; if (name() == "Sun" || name() == "Moon") minsize = 8.; if (size < minsize) size = minsize; //Inflate offset for Saturn if (name() == i18n("Saturn")) size = int(2.5 * size); return 0.5 * size + 4.; } void KSPlanetBase::findPhase() { if (2*rsun()*rearth() == 0) { Phase = std::numeric_limits::quiet_NaN(); return; } /* Compute the phase of the planet in degrees */ double earthSun = KStarsData::Instance()->skyComposite()->earth()->rsun(); double cosPhase = (rsun() * rsun() + rearth() * rearth() - earthSun * earthSun) / (2 * rsun() * rearth()); Phase = acos(cosPhase) * 180.0 / dms::PI; /* More elegant way of doing it, but requires the Sun. TODO: Switch to this if and when we make KSSun a singleton */ // Phase = ecLong()->Degrees() - Sun->ecLong()->Degrees(); } diff --git a/kstars/skyobjects/ksplanetbase.h b/kstars/skyobjects/ksplanetbase.h index 6c2badbf1..b54fbea91 100644 --- a/kstars/skyobjects/ksplanetbase.h +++ b/kstars/skyobjects/ksplanetbase.h @@ -1,284 +1,284 @@ /*************************************************************************** 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 #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 {} 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, 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. * @param Obliquity current Obliquity of the Ecliptic (angle from Equator) */ void EclipticToEquatorial(const CachingDms *Obliquity); /** * @short Convert Right Ascension/Declination to Ecliptic logitude/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() */ - void updateCoords(const KSNumbers *num, bool includePlanets = true, const CachingDms *lat = 0, - const CachingDms *LST = 0, bool forceRecompute = false) override; + 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 = 0, const CachingDms *LST = 0, - const KSPlanetBase *Earth = 0); + 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(); // 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; double Phase; 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/skyline.h b/kstars/skyobjects/skyline.h index 099ecd0cf..22bb98b65 100644 --- a/kstars/skyobjects/skyline.h +++ b/kstars/skyobjects/skyline.h @@ -1,80 +1,80 @@ /*************************************************************************** skyline.h - K Desktop Planetarium ------------------- begin : Mon June 26 2006 copyright : (C) 2006 by Jason Harris email : kstarss@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. * * * ***************************************************************************/ #ifndef SKYLINE_H_ #define SKYLINE_H_ #include "skypoint.h" class dms; class KStarsData; /** * @class SkyLine * * A series of connected line segments in the sky, composed of SkyPoints at * its vertices. SkyLines are used for constellation lines and boundaries, * the coordinate grid, and the equator, ecliptic and horizon. * * @note the SkyLine segments are always straight lines, they are not * Great Circle segments joining the two endpoints. Therefore, line segments * that need to follow great circles must be approximated with many short * SkyLine segments. */ class SkyLine { public: /** Default Constructor (empty). */ SkyLine(); /** Destructor */ ~SkyLine(); /**Append a segment to the list by adding a new endpoint. * @param p the new endpoint to be added */ void append(SkyPoint *p); /** @return a const pointer to a point in the SkyLine * param i the index position of the point */ inline SkyPoint *point(int i) const { return m_pList[i]; } inline QList &points() { return m_pList; } /** Remove all points from list */ void clear(); /**Set point i in the SkyLine * @param i the index position of the point to modify * @param p1 the new SkyPoint */ void setPoint(int i, SkyPoint *p); /** @return the angle subtended by any line segment along the SkyLine. * @param i the index of the line segment to be measured. * If no argument is given, the first segment is assumed. */ dms angularSize(int i = 0) const; - void update(KStarsData *data, KSNumbers *num = 0); + void update(KStarsData *data, KSNumbers *num = nullptr); private: QList m_pList; }; #endif diff --git a/kstars/skyobjects/skyobject.h b/kstars/skyobjects/skyobject.h index c4c3a8ea5..a4779364f 100644 --- a/kstars/skyobjects/skyobject.h +++ b/kstars/skyobjects/skyobject.h @@ -1,477 +1,477 @@ /*************************************************************************** skyobject.h - K Desktop Planetarium ------------------- begin : Sun Feb 11 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 "auxinfo.h" #include "dms.h" #include "skypoint.h" #include #include #include #include class QPoint; class GeoLocation; class KStarsDateTime; class KSPopupMenu; /** * @class SkyObject * Provides all necessary information about an object in the sky: * its coordinates, name(s), type, magnitude, and QStringLists of * URLs for images and webpages regarding the object. * @short Information about an object in the sky. * @author Jason Harris * @version 1.0 */ class SkyObject : public SkyPoint { public: /** * @short Type for Unique object IDenticator. * * Each object has unique ID (UID). For different objects UIDs must be different. */ typedef qint64 UID; /** @short Kind of UID */ static const UID UID_STAR; static const UID UID_GALAXY; static const UID UID_DEEPSKY; static const UID UID_SOLARSYS; /** Invalid UID. Real sky object could not have such UID */ static const UID invalidUID; /** * Constructor. Set SkyObject data according to arguments. * @param t Type of object * @param r catalog Right Ascension * @param d catalog Declination * @param m magnitude (brightness) * @param n Primary name * @param n2 Secondary name * @param lname Long name (common name) */ explicit SkyObject(int t = TYPE_UNKNOWN, dms r = dms(0.0), dms d = dms(0.0), float m = 0.0, const QString &n = QString(), const QString &n2 = QString(), const QString &lname = QString()); /** * Constructor. Set SkyObject data according to arguments. Differs from * above function only in data type of RA and Dec. * @param t Type of object * @param r catalog Right Ascension * @param d catalog Declination * @param m magnitude (brightness) * @param n Primary name * @param n2 Secondary name * @param lname Long name (common name) */ SkyObject(int t, double r, double d, float m = 0.0, const QString &n = QString(), const QString &n2 = QString(), const QString &lname = QString()); /** Destructor (empty) */ ~SkyObject() override; /** * @short Create copy of object. * This method is virtual copy constructor. It allows for safe * copying of objects. In other words, KSPlanet object stored in * SkyObject pointer will be copied as KSPlanet. * * Each subclass of SkyObject MUST implement clone method. There * is no checking to ensure this, though. * * @return pointer to newly allocated object. Caller takes full responsibility * for deallocating it. */ virtual SkyObject *clone() const; /** * @enum TYPE * The type classification of the SkyObject. * @note Keep TYPE_UNKNOWN at 255. To find out how many known * types exist, keep the NUMBER_OF_KNOWN_TYPES at the highest * non-Unknown value. This is a fake type that can be used in * comparisons and for loops. */ enum TYPE { STAR = 0, CATALOG_STAR = 1, PLANET = 2, OPEN_CLUSTER = 3, GLOBULAR_CLUSTER = 4, GASEOUS_NEBULA = 5, PLANETARY_NEBULA = 6, SUPERNOVA_REMNANT = 7, GALAXY = 8, COMET = 9, ASTEROID = 10, CONSTELLATION = 11, MOON = 12, ASTERISM = 13, GALAXY_CLUSTER = 14, DARK_NEBULA = 15, QUASAR = 16, MULT_STAR = 17, RADIO_SOURCE = 18, SATELLITE = 19, SUPERNOVA = 20, NUMBER_OF_KNOWN_TYPES = 21, TYPE_UNKNOWN = 255 }; /** * @return A translated string indicating the type name for a given type number * @param t The type number * @note Note the existence of a SkyObject::typeName( void ) method that is not static and returns the type of this object. */ static QString typeName(const int t); /** @return object's primary name. */ inline virtual QString name(void) const { return hasName() ? Name : unnamedString; } /** @return object's primary name, translated to local language. */ inline QString translatedName() const { return i18n( name() .toUtf8()); // FIXME: Hmm... that's funny. How does the string extraction work, if we are UTF8-ing the name first? Does the string extraction change to UTF8? } /** @return object's secondary name */ inline QString name2(void) const { return (hasName2() ? Name2 : emptyString); } /** @return object's secondary name, translated to local language. */ inline QString translatedName2() const { return (hasName2() ? i18n(Name2.toUtf8()) : emptyString); } /** * @return object's common (long) name */ virtual QString longname(void) const { return hasLongName() ? LongName : unnamedObjectString; } /** * @return object's common (long) name, translated to local language. */ QString translatedLongName() const { return i18n(longname().toUtf8()); } /** * Set the object's long name. * @param longname the object's long name. */ void setLongName(const QString &longname = QString()); /** * @return the string used to label the object on the map * In the default implementation, this just returns translatedName() * Overridden by StarObject. */ virtual QString labelString() const; /** * @return object's type identifier (int) * @see enum TYPE */ inline int type(void) const { return (int)Type; } /** * Set the object's type identifier to the argument. * @param t the object's type identifier (e.g., "SkyObject::PLANETARY_NEBULA") * @see enum TYPE */ inline void setType(int t) { Type = (unsigned char)t; } /** * @return the type name for this object * @note This just calls the static method by the same name, with the appropriate type number. See SkyObject::typeName( const int ) */ QString typeName() const; /** * @return object's magnitude */ inline float mag() const { return sortMagnitude; } /** * @return the object's position angle. This is overridden in KSPlanetBase * and DeepSkyObject; for all other SkyObjects, this returns 0.0. */ inline virtual double pa() const { return 0.0; } /** * @return true if the object is a solar system body. */ inline bool isSolarSystem() const { return (type() == 2 || type() == 9 || type() == 10 || type() == 12); } /** * Initialize the popup menut. This function should call correct * initialization function in KSPopupMenu. By overloading the * function, we don't have to check the object type when we need * the menu. */ virtual void initPopupMenu(KSPopupMenu *pmenu); /** Show Type-specific popup menu. Oveloading is done in the function initPopupMenu */ void showPopupMenu(KSPopupMenu *pmenu, const QPoint &pos); /** * Determine the time at which the point will rise or set. Because solar system * objects move across the sky, it is necessary to iterate on the solution. * We compute the rise/set time for the object's current position, then * compute the object's position at that time. Finally, we recompute then * rise/set time for the new coordinates. Further iteration is not necessary, * even for the most swiftly-moving object (the Moon). * @return the local time that the object will rise * @param dt current UT date/time * @param geo current geographic location * @param rst If true, compute rise time. If false, compute set time. * @param exact If true, use a second iteration for more accurate time */ QTime riseSetTime(const KStarsDateTime &dt, const GeoLocation *geo, bool rst, bool exact = true) const; /** * @return the UT time when the object will rise or set * @param dt target date/time * @param geo pointer to Geographic location * @param rst Boolean. If true will compute rise time. If false * will compute set time. * @param exact If true, use a second iteration for more accurate time */ QTime riseSetTimeUT(const KStarsDateTime &dt, const GeoLocation *geo, bool rst, bool exact = true) const; /** * @return the Azimuth time when the object will rise or set. This function * recomputes set or rise UT times. * @param dt target date/time * @param geo GeoLocation object * @param rst Boolen. If true will compute rise time. If false * will compute set time. */ dms riseSetTimeAz(const KStarsDateTime &dt, const GeoLocation *geo, bool rst) const; /** * The same iteration technique described in riseSetTime() is used here. * @return the local time that the object will transit the meridian. * @param dt target date/time * @param geo pointer to the geographic location */ QTime transitTime(const KStarsDateTime &dt, const GeoLocation *geo) const; /** * @return the universal time that the object will transit the meridian. * @param dt target date/time * @param geo pointer to the geographic location */ QTime transitTimeUT(const KStarsDateTime &dt, const GeoLocation *geo) const; /** * @return the altitude of the object at the moment it transits the meridian. * @param dt target date/time * @param geo pointer to the geographic location */ dms transitAltitude(const KStarsDateTime &dt, const GeoLocation *geo) const; /** * The equatorial coordinates for the object on date dt are computed and returned, * but the object's internal coordinates are not modified. * @return the coordinates of the selected object for the time given by jd * @param dt date/time for which the coords will be computed. * @param geo pointer to geographic location (used for solar system only) * @note Does not update the horizontal coordinates. Call EquatorialToHorizontal for that. */ - SkyPoint recomputeCoords(const KStarsDateTime &dt, const GeoLocation *geo = 0) const; + SkyPoint recomputeCoords(const KStarsDateTime &dt, const GeoLocation *geo = nullptr) const; /** * @short Like recomputeCoords, but also calls EquatorialToHorizontal before returning */ SkyPoint recomputeHorizontalCoords(const KStarsDateTime &dt, const GeoLocation *geo) const; inline bool hasName() const { return !Name.isEmpty(); } inline bool hasName2() const { return !Name2.isEmpty(); } inline bool hasLongName() const { return !LongName.isEmpty(); } /** * @short Given the Image title from a URL file, try to convert it to an image credit string. */ QString messageFromTitle(const QString &imageTitle) const; /** * @short Save new user log text */ void saveUserLog(const QString &newLog); /** * @return the pixel distance for offseting the object's name label * @note overridden in StarObject, DeepSkyObject, KSPlanetBase */ virtual double labelOffset() const; /** * @short Query whether this SkyObject has a valid AuxInfo structure associated with it. * @return true if this SkyObject has a valid AuxInfo structure associated with it, false if not. */ inline bool hasAuxInfo() const { return !(!info); } /** * @return A reference to a QStringList storing a list of Image URLs associated with this SkyObject. */ inline QStringList &ImageList() { return getAuxInfo()->ImageList; } /** * @return A reference to a QStringList storing a list of Image Titles associated with this SkyObject. */ inline QStringList &ImageTitle() { return getAuxInfo()->ImageTitle; } /** * @return A reference to a QStringList storing a list of Information Links associated with * this SkyObject. */ inline QStringList &InfoList() { return getAuxInfo()->InfoList; } /** * @return A reference to a QStringList storing a list of Information Link Titles associated with * this SkyObject. */ inline QStringList &InfoTitle() { return getAuxInfo()->InfoTitle; } /** * @return a reference to a QString storing the users' log for this SkyObject */ inline QString &userLog() { return getAuxInfo()->userLog; } inline QString ¬es() { return getAuxInfo()->notes; } void setNotes(QString _notes) { getAuxInfo()->notes = _notes; } /** * @short Return UID for object. * This method should be reimplemented in all concrete * subclasses. Implementation for SkyObject just returns * invalidUID. It's required SkyObject is not an abstract class. */ virtual UID getUID() const; private: /** * Compute the UT time when the object will rise or set. It is an auxiliary * procedure because it does not use the RA and DEC of the object but values * given as parameters. You may want to use riseSetTimeUT() which is * public. riseSetTimeUT() calls this function iteratively. * @param dt target date/time * @param geo pointer to Geographic location * @param righta pointer to Right ascention of the object * @param decl pointer to Declination of the object * @param rst Boolean. If true will compute rise time. If false * will compute set time. * @return the time at which the given position will rise or set. */ QTime auxRiseSetTimeUT(const KStarsDateTime &dt, const GeoLocation *geo, const dms *righta, const dms *decl, bool riseT) const; /** * Compute the LST time when the object will rise or set. It is an auxiliary * procedure because it does not use the RA and DEC of the object but values * given as parameters. You may want to use riseSetTimeLST() which is * public. riseSetTimeLST() calls this function iteratively. * @param gLt Geographic latitude * @param rga Right ascention of the object * @param decl Declination of the object * @param rst Boolean. If true will compute rise time. If false * will compute set time. */ dms auxRiseSetTimeLST(const dms *gLt, const dms *rga, const dms *decl, bool rst) const; /** * Compute the approximate hour angle that an object with declination d will have * when its altitude is h (as seen from geographic latitude gLat). * This function is only used by auxRiseSetTimeLST(). * @param h pointer to the altitude of the object * @param gLat pointer to the geographic latitude * @param d pointer to the declination of the object. * @return the Hour Angle, in degrees. */ double approxHourAngle(const dms *h, const dms *gLat, const dms *d) const; /** * Correct for the geometric altitude of the center of the body at the * time of rising or setting. This is due to refraction at the horizon * and to the size of the body. The moon correction has also to take into * account parallax. The value we use here is a rough approximation * suggested by J. Meeus. * * Weather status (temperature and pressure basically) is not taken * into account although change of conditions between summer and * winter could shift the times of sunrise and sunset by 20 seconds. * * This function is only used by auxRiseSetTimeLST(). * @return dms object with the correction. */ dms elevationCorrection(void) const; /** * @short Return a pointer to the AuxInfo object associated with this SkyObject. * @note This method creates the AuxInfo object if it is non-existent * @return Pointer to an AuxInfo structure */ AuxInfo *getAuxInfo(); // FIXME: Can this be made conceptually const or the like? unsigned char Type; float sortMagnitude; // This magnitude is used for sorting / making decisions about the visibility of an object. Should not be NaN. protected: /** * Set the object's sorting magnitude. * @param m the object's magnitude. */ inline void setMag(float m) { sortMagnitude = m < 36.0 ? m : NaN:: f; // Updating faintest sane magnitude to 36.0 (faintest visual magnitude visible with E-ELT, acc. to Wikipedia on Apparent Magnitude.) } // FIXME: We claim sortMagnitude should not be NaN, but we are setting it to NaN above!! ^ /** * Set the object's primary name. * @param name the object's primary name */ inline void setName(const QString &name) { Name = name; } /** * Set the object's secondary name. * @param name2 the object's secondary name. */ inline void setName2(const QString &name2 = QString()) { Name2 = name2; } QString Name, Name2, LongName; // Pointer to an auxiliary info structure that stores Image URLs, Info URLs etc. QSharedDataPointer info; // store often used name strings in static variables static QString emptyString; static QString unnamedString; static QString unnamedObjectString; static QString starString; }; diff --git a/kstars/skyobjects/skypoint.cpp b/kstars/skyobjects/skypoint.cpp index 18cbd3226..6a619df0b 100644 --- a/kstars/skyobjects/skypoint.cpp +++ b/kstars/skyobjects/skypoint.cpp @@ -1,942 +1,942 @@ /*************************************************************************** skypoint.cpp - K Desktop Planetarium ------------------- begin : Sun Feb 11 2001 copyright : (C) 2001-2005 by Jason Harris email : jharris@30doradus.org copyright : (C) 2004-2005 by Pablo de Vicente email : p.devicente@wanadoo.es ***************************************************************************/ /*************************************************************************** * * * 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 "skypoint.h" #include "dms.h" #include "ksnumbers.h" #include "kstarsdatetime.h" #include "kssun.h" #include "kstarsdata.h" #include "Options.h" #include "skyobject.h" #include "skycomponents/skymapcomposite.h" #include #include #include #ifdef PROFILE_COORDINATE_CONVERSION #include // For profiling, remove if not profiling. long unsigned SkyPoint::eqToHzCalls = 0; double SkyPoint::cpuTime_EqToHz = 0.; #endif -KSSun *SkyPoint::m_Sun = 0; +KSSun *SkyPoint::m_Sun = nullptr; const double SkyPoint::altCrit = -1.0; SkyPoint::SkyPoint() { // Default constructor. Set nonsense values RA0.setD(-1); // RA >= 0 always :-) Dec0.setD(180); // Dec is between -90 and 90 Degrees :-) RA = RA0; Dec = Dec0; lastPrecessJD = J2000; // By convention, we use J2000 coordinates } SkyPoint::~SkyPoint() { } void SkyPoint::set(const dms &r, const dms &d) { RA0 = RA = r; Dec0 = Dec = d; lastPrecessJD = J2000; // By convention, we use J2000 coordinates } void SkyPoint::EquatorialToHorizontal(const dms *LST, const dms *lat) { // qDebug() << "NOTE: This EquatorialToHorizontal overload (using dms pointers instead of CachingDms pointers) is deprecated and should be replaced with CachingDms prototype wherever speed is desirable!"; CachingDms _LST(*LST), _lat(*lat); EquatorialToHorizontal(&_LST, &_lat); } void SkyPoint::EquatorialToHorizontal(const CachingDms *LST, const CachingDms *lat) { #ifdef PROFILE_COORDINATE_CONVERSION std::clock_t start = std::clock(); #endif //Uncomment for spherical trig version double AltRad, AzRad; double sindec, cosdec, sinlat, coslat, sinHA, cosHA; double sinAlt, cosAlt; CachingDms HourAngle = (*LST) - ra(); // Using CachingDms subtraction operator to find cos/sin of HourAngle without calling sincos() lat->SinCos(sinlat, coslat); dec().SinCos(sindec, cosdec); HourAngle.SinCos(sinHA, cosHA); sinAlt = sindec * sinlat + cosdec * coslat * cosHA; AltRad = asin(sinAlt); cosAlt = sqrt( 1 - sinAlt * sinAlt); // Avoid trigonometric function. Return value of asin is always in [-pi/2, pi/2] and in this domain cosine is always non-negative, so we can use this. if (cosAlt == 0) cosAlt = cos(AltRad); double arg = (sindec - sinlat * sinAlt) / (coslat * cosAlt); if (arg <= -1.0) AzRad = dms::PI; else if (arg >= 1.0) AzRad = 0.0; else AzRad = acos(arg); if (sinHA > 0.0) AzRad = 2.0 * dms::PI - AzRad; // resolve acos() ambiguity Alt.setRadians(AltRad); Az.setRadians(AzRad); #ifdef PROFILE_COORDINATE_CONVERSION std::clock_t stop = std::clock(); cpuTime_EqToHz += double(stop - start) / double(CLOCKS_PER_SEC); // Accumulate time in seconds ++eqToHzCalls; #endif // //Uncomment for XYZ version // double xr, yr, zr, xr1, zr1, sa, ca; // //Z-axis rotation by -LST // dms a = dms( -1.0*LST->Degrees() ); // a.SinCos( sa, ca ); // xr1 = m_X*ca + m_Y*sa; // yr = -1.0*m_X*sa + m_Y*ca; // zr1 = m_Z; // // //Y-axis rotation by lat - 90. // a = dms( lat->Degrees() - 90.0 ); // a.SinCos( sa, ca ); // xr = xr1*ca - zr1*sa; // zr = xr1*sa + zr1*ca; // // //FIXME: eventually, we will work with XYZ directly // Alt.setRadians( asin( zr ) ); // Az.setRadians( atan2( yr, xr ) ); } void SkyPoint::HorizontalToEquatorial(const dms *LST, const dms *lat) { double HARad, DecRad; double sinlat, coslat, sinAlt, cosAlt, sinAz, cosAz; double sinDec, cosDec; lat->SinCos(sinlat, coslat); alt().SinCos(sinAlt, cosAlt); Az.SinCos(sinAz, cosAz); sinDec = sinAlt * sinlat + cosAlt * coslat * cosAz; DecRad = asin(sinDec); cosDec = cos(DecRad); Dec.setRadians(DecRad); double x = (sinAlt - sinlat * sinDec) / (coslat * cosDec); //Under certain circumstances, x can be very slightly less than -1.0000, or slightly //greater than 1.0000, leading to a crash on acos(x). However, the value isn't //*really* out of range; it's a kind of roundoff error. if (x < -1.0 && x > -1.000001) HARad = dms::PI; else if (x > 1.0 && x < 1.000001) HARad = 0.0; else if (x < -1.0) { //qWarning() << "Coordinate out of range."; HARad = dms::PI; } else if (x > 1.0) { //qWarning() << "Coordinate out of range."; HARad = 0.0; } else HARad = acos(x); if (sinAz > 0.0) HARad = 2.0 * dms::PI - HARad; // resolve acos() ambiguity RA.setRadians(LST->radians() - HARad); RA.reduceToRange(dms::ZERO_TO_2PI); } void SkyPoint::findEcliptic(const CachingDms *Obliquity, dms &EcLong, dms &EcLat) { double sinRA, cosRA, sinOb, cosOb, sinDec, cosDec, tanDec; ra().SinCos(sinRA, cosRA); dec().SinCos(sinDec, cosDec); Obliquity->SinCos(sinOb, cosOb); tanDec = sinDec / cosDec; // FIXME: -jbb div by zero? double y = sinRA * cosOb + tanDec * sinOb; double ELongRad = atan2(y, cosRA); EcLong.setRadians(ELongRad); EcLong.reduceToRange(dms::ZERO_TO_2PI); EcLat.setRadians(asin(sinDec * cosOb - cosDec * sinOb * sinRA)); } void SkyPoint::setFromEcliptic(const CachingDms *Obliquity, const dms &EcLong, const dms &EcLat) { double sinLong, cosLong, sinLat, cosLat, sinObliq, cosObliq; EcLong.SinCos(sinLong, cosLong); EcLat.SinCos(sinLat, cosLat); Obliquity->SinCos(sinObliq, cosObliq); double sinDec = sinLat * cosObliq + cosLat * sinObliq * sinLong; double y = sinLong * cosObliq - (sinLat / cosLat) * sinObliq; // double RARad = atan2( y, cosLong ); RA.setUsing_atan2(y, cosLong); RA.reduceToRange(dms::ZERO_TO_2PI); Dec.setUsing_asin(sinDec); } void SkyPoint::precess(const KSNumbers *num) { double cosRA0, sinRA0, cosDec0, sinDec0; const Eigen::Matrix3d &precessionMatrix = num->p2(); Eigen::Vector3d v, s; RA0.SinCos(sinRA0, cosRA0); Dec0.SinCos(sinDec0, cosDec0); s[0] = cosRA0 * cosDec0; s[1] = sinRA0 * cosDec0; s[2] = sinDec0; // NOTE: Rotation matrices are the fastest way to do rotations on // a vector. Quaternions need more multiplications. The rotation // matrix compensates in some sense by having more 'precomputed' // multiplications. The matrix elements seem to cache nicely, so // there isn't much overhead in accessing them. //Multiply P2 and s to get v, the vector representing the new coords. // for ( unsigned int i=0; i<3; ++i ) { // v[i] = 0.0; // for (uint j=0; j< 3; ++j) { // v[i] += num->p2( j, i )*s[j]; // } // } v.noalias() = precessionMatrix * s; //Extract RA, Dec from the vector: RA.setUsing_atan2(v[1], v[0]); RA.reduceToRange(dms::ZERO_TO_2PI); Dec.setUsing_asin(v[2]); } SkyPoint SkyPoint::deprecess(const KSNumbers *num, long double epoch) { SkyPoint p1(RA, Dec); long double now = num->julianDay(); p1.precessFromAnyEpoch(now, epoch); if ((std::isnan(RA0.Degrees()) || std::isnan(Dec0.Degrees())) || (!std::isnan(Dec0.Degrees()) && fabs(Dec0.Degrees()) > 90.0)) { // We have invalid RA0 and Dec0, so set them if epoch = J2000. Otherwise, do not touch. if (epoch == J2000) { RA0 = p1.ra(); Dec0 = p1.dec(); } } return p1; } void SkyPoint::nutate(const KSNumbers *num) { double cosRA, sinRA, cosDec, sinDec, tanDec; double cosOb, sinOb; RA.SinCos(sinRA, cosRA); Dec.SinCos(sinDec, cosDec); num->obliquity()->SinCos(sinOb, cosOb); //Step 2: Nutation if (fabs(Dec.Degrees()) < 80.0) //approximate method { tanDec = sinDec / cosDec; double dRA = num->dEcLong() * (cosOb + sinOb * sinRA * tanDec) - num->dObliq() * cosRA * tanDec; double dDec = num->dEcLong() * (sinOb * cosRA) + num->dObliq() * sinRA; RA.setD(RA.Degrees() + dRA); Dec.setD(Dec.Degrees() + dDec); } else //exact method { dms EcLong, EcLat; findEcliptic(num->obliquity(), EcLong, EcLat); //Add dEcLong to the Ecliptic Longitude dms newLong(EcLong.Degrees() + num->dEcLong()); setFromEcliptic(num->obliquity(), newLong, EcLat); } } SkyPoint SkyPoint::moveAway(const SkyPoint &from, double dist) const { CachingDms lat1, dtheta; if (dist == 0.0) { qDebug() << "moveAway called with zero distance!"; return *this; } double dst = fabs(dist * dms::DegToRad / 3600.0); // In radian // Compute the bearing angle w.r.t. the RA axis ("latitude") CachingDms dRA(ra() - from.ra()); CachingDms dDec(dec() - from.dec()); double bearing = atan2(dRA.sin() / dRA.cos(), dDec.sin()); // Do not use dRA = PI / 2!! //double bearing = atan2( dDec.radians() , dRA.radians() ); // double dir0 = (dist >= 0 ) ? bearing : bearing + dms::PI; // in radian double dir0 = bearing + std::signbit(dist) * dms::PI; // might be faster? double sinDst = sin(dst), cosDst = cos(dst); lat1.setUsing_asin(dec().sin() * cosDst + dec().cos() * sinDst * cos(dir0)); dtheta.setUsing_atan2(sin(dir0) * sinDst * dec().cos(), cosDst - dec().sin() * lat1.sin()); return SkyPoint(ra() + dtheta, lat1); } bool SkyPoint::checkBendLight() { // First see if we are close enough to the sun to bother about the // gravitational lensing effect. We correct for the effect at // least till b = 10 solar radii, where the effect is only about // 0.06". Assuming min. sun-earth distance is 200 solar radii. static const dms maxAngle(1.75 * (30.0 / 200.0) / dms::DegToRad); if (!m_Sun) { SkyComposite *skycomopsite = KStarsData::Instance()->skyComposite(); if (skycomopsite == nullptr) return false; m_Sun = (KSSun *)skycomopsite->findByName("Sun"); if (m_Sun == nullptr) return false; } // TODO: This can be optimized further. We only need a ballpark estimate of the distance to the sun to start with. return (fabs(angularDistanceTo(static_cast(m_Sun)).Degrees()) <= maxAngle.Degrees()); // NOTE: dynamic_cast is slow and not important here. } bool SkyPoint::bendlight() { // NOTE: This should be applied before aberration // NOTE: One must call checkBendLight() before unnecessarily calling this. // We correct for GR effects // NOTE: This code is buggy. The sun needs to be initialized to // the current epoch -- but we are not certain that this is the // case. We have, as of now, no way of telling if the sun is // initialized or not. If we initialize the sun here, we will be // slowing down the program rather substantially and potentially // introducing bugs. Therefore, we just ignore this problem, and // hope that whenever the user is interested in seeing the effects // of GR, we have the sun initialized correctly. This is usually // the case. When the sun is not correctly initialized, rearth() // is not computed, so we just assume it is nominally equal to 1 // AU to get a reasonable estimate. Q_ASSERT(m_Sun); double corr_sec = 1.75 * m_Sun->physicalSize() / ((std::isfinite(m_Sun->rearth()) ? m_Sun->rearth() : 1) * AU_KM * angularDistanceTo(static_cast(m_Sun)).sin()); Q_ASSERT(corr_sec > 0); SkyPoint sp = moveAway(*m_Sun, corr_sec); setRA(sp.ra()); setDec(sp.dec()); return true; } void SkyPoint::aberrate(const KSNumbers *num) { double cosRA, sinRA, cosDec, sinDec; double cosOb, sinOb, cosL, sinL, cosP, sinP; double K = num->constAberr().Degrees(); //constant of aberration double e = num->earthEccentricity(); RA.SinCos(sinRA, cosRA); Dec.SinCos(sinDec, cosDec); num->obliquity()->SinCos(sinOb, cosOb); // double tanOb = sinOb/cosOb; num->sunTrueLongitude().SinCos(sinL, cosL); num->earthPerihelionLongitude().SinCos(sinP, cosP); //Step 3: Aberration // double dRA = -1.0 * K * ( cosRA * cosL * cosOb + sinRA * sinL )/cosDec // + e * K * ( cosRA * cosP * cosOb + sinRA * sinP )/cosDec; // double dDec = -1.0 * K * ( cosL * cosOb * ( tanOb * cosDec - sinRA * sinDec ) + cosRA * sinDec * sinL ) // + e * K * ( cosP * cosOb * ( tanOb * cosDec - sinRA * sinDec ) + cosRA * sinDec * sinP ); double dRA = K * (cosRA * cosOb / cosDec) * (e * cosP - cosL); double dDec = K * (sinRA * (sinOb * cosDec - cosOb * sinDec) * (e * cosP - cosL) + cosRA * sinDec * (e * sinP - sinL)); RA.setD(RA.Degrees() + dRA); Dec.setD(Dec.Degrees() + dDec); } // Note: This method is one of the major rate determining factors in how fast the map pans / zooms in or out void SkyPoint::updateCoords(const KSNumbers *num, bool /*includePlanets*/, const CachingDms *lat, const CachingDms *LST, bool forceRecompute) { //Correct the catalog coordinates for the time-dependent effects //of precession, nutation and aberration bool recompute, lens; // NOTE: The same short-circuiting checks are also implemented in // StarObject::JITUpdate(), even before calling // updateCoords(). While this is code-duplication, these bits of // code need to be really optimized, at least for stars. For // optimization purposes, the code is left duplicated in two // places. Please be wary of changing one without changing the // other. Q_ASSERT(std::isfinite(lastPrecessJD)); if (Options::useRelativistic() && checkBendLight()) { recompute = true; lens = true; } else { recompute = (Options::alwaysRecomputeCoordinates() || forceRecompute || std::abs(lastPrecessJD - num->getJD()) >= 0.00069444); // Update once per solar minute lens = false; } if (recompute) { precess(num); nutate(num); if (lens) bendlight(); // FIXME: Shouldn't we apply this on the horizontal coordinates? aberrate(num); lastPrecessJD = num->getJD(); Q_ASSERT(std::isfinite(RA.Degrees()) && std::isfinite(Dec.Degrees())); } if (lat || LST) qWarning() << i18n("lat and LST parameters should only be used in KSPlanetBase objects."); } void SkyPoint::precessFromAnyEpoch(long double jd0, long double jdf) { double cosRA, sinRA, cosDec, sinDec; double v[3], s[3]; RA = RA0; Dec = Dec0; // Is this necessary? if (jd0 == jdf) return; RA.SinCos(sinRA, cosRA); Dec.SinCos(sinDec, cosDec); if (jd0 == B1950) { B1950ToJ2000(); jd0 = J2000; RA.SinCos(sinRA, cosRA); Dec.SinCos(sinDec, cosDec); } if (jd0 != jdf) { // The original coordinate is referred to the FK5 system and // is NOT J2000. if (jd0 != J2000) { //v is a column vector representing input coordinates. v[0] = cosRA * cosDec; v[1] = sinRA * cosDec; v[2] = sinDec; //Need to first precess to J2000.0 coords //s is the product of P1 and v; s represents the //coordinates precessed to J2000 KSNumbers num(jd0); for (unsigned int i = 0; i < 3; ++i) { s[i] = num.p1(0, i) * v[0] + num.p1(1, i) * v[1] + num.p1(2, i) * v[2]; } //Input coords already in J2000, set s accordingly. } else { s[0] = cosRA * cosDec; s[1] = sinRA * cosDec; s[2] = sinDec; } if (jdf == B1950) { RA.setRadians(atan2(s[1], s[0])); Dec.setRadians(asin(s[2])); J2000ToB1950(); return; } KSNumbers num(jdf); for (unsigned int i = 0; i < 3; ++i) { v[i] = num.p2(0, i) * s[0] + num.p2(1, i) * s[1] + num.p2(2, i) * s[2]; } RA.setUsing_atan2(v[1], v[0]); Dec.setUsing_asin(v[2]); RA.reduceToRange(dms::ZERO_TO_2PI); return; } } void SkyPoint::apparentCoord(long double jd0, long double jdf) { precessFromAnyEpoch(jd0, jdf); KSNumbers num(jdf); nutate(&num); if (Options::useRelativistic() && checkBendLight()) bendlight(); aberrate(&num); } void SkyPoint::Equatorial1950ToGalactic(dms &galLong, dms &galLat) { double a = 192.25; double sinb, cosb, sina_RA, cosa_RA, sinDEC, cosDEC, tanDEC; dms c(303.0); dms b(27.4); tanDEC = tan(Dec.radians()); b.SinCos(sinb, cosb); dms(a - RA.Degrees()).SinCos(sina_RA, cosa_RA); Dec.SinCos(sinDEC, cosDEC); galLong.setRadians(c.radians() - atan2(sina_RA, cosa_RA * sinb - tanDEC * cosb)); galLong.reduceToRange(dms::ZERO_TO_2PI); galLat.setRadians(asin(sinDEC * sinb + cosDEC * cosb * cosa_RA)); } void SkyPoint::GalacticToEquatorial1950(const dms *galLong, const dms *galLat) { double a = 123.0; double sinb, cosb, singLat, cosgLat, tangLat, singLong_a, cosgLong_a; dms c(12.25); dms b(27.4); tangLat = tan(galLat->radians()); galLat->SinCos(singLat, cosgLat); dms(galLong->Degrees() - a).SinCos(singLong_a, cosgLong_a); b.SinCos(sinb, cosb); RA.setRadians(c.radians() + atan2(singLong_a, cosgLong_a * sinb - tangLat * cosb)); RA.reduceToRange(dms::ZERO_TO_2PI); Dec.setRadians(asin(singLat * sinb + cosgLat * cosb * cosgLong_a)); } void SkyPoint::B1950ToJ2000(void) { double cosRA, sinRA, cosDec, sinDec; // double cosRA0, sinRA0, cosDec0, sinDec0; double v[3], s[3]; // 1984 January 1 0h KSNumbers num(2445700.5); // Eterms due to aberration addEterms(); RA.SinCos(sinRA, cosRA); Dec.SinCos(sinDec, cosDec); // Precession from B1950 to J1984 s[0] = cosRA * cosDec; s[1] = sinRA * cosDec; s[2] = sinDec; for (unsigned int i = 0; i < 3; ++i) { v[i] = num.p2b(0, i) * s[0] + num.p2b(1, i) * s[1] + num.p2b(2, i) * s[2]; } // RA zero-point correction at 1984 day 1, 0h. RA.setRadians(atan2(v[1], v[0])); Dec.setRadians(asin(v[2])); RA.setH(RA.Hours() + 0.06390 / 3600.); RA.SinCos(sinRA, cosRA); Dec.SinCos(sinDec, cosDec); s[0] = cosRA * cosDec; s[1] = sinRA * cosDec; s[2] = sinDec; // Precession from 1984 to J2000. for (unsigned int i = 0; i < 3; ++i) { v[i] = num.p1(0, i) * s[0] + num.p1(1, i) * s[1] + num.p1(2, i) * s[2]; } RA.setRadians(atan2(v[1], v[0])); Dec.setRadians(asin(v[2])); } void SkyPoint::J2000ToB1950(void) { double cosRA, sinRA, cosDec, sinDec; // double cosRA0, sinRA0, cosDec0, sinDec0; double v[3], s[3]; // 1984 January 1 0h KSNumbers num(2445700.5); RA.SinCos(sinRA, cosRA); Dec.SinCos(sinDec, cosDec); s[0] = cosRA * cosDec; s[1] = sinRA * cosDec; s[2] = sinDec; // Precession from J2000 to 1984 day, 0h. for (unsigned int i = 0; i < 3; ++i) { v[i] = num.p2(0, i) * s[0] + num.p2(1, i) * s[1] + num.p2(2, i) * s[2]; } RA.setRadians(atan2(v[1], v[0])); Dec.setRadians(asin(v[2])); // RA zero-point correction at 1984 day 1, 0h. RA.setH(RA.Hours() - 0.06390 / 3600.); RA.SinCos(sinRA, cosRA); Dec.SinCos(sinDec, cosDec); // Precession from B1950 to J1984 s[0] = cosRA * cosDec; s[1] = sinRA * cosDec; s[2] = sinDec; for (unsigned int i = 0; i < 3; ++i) { v[i] = num.p1b(0, i) * s[0] + num.p1b(1, i) * s[1] + num.p1b(2, i) * s[2]; } RA.setRadians(atan2(v[1], v[0])); Dec.setRadians(asin(v[2])); // Eterms due to aberration subtractEterms(); } SkyPoint SkyPoint::Eterms(void) { double sd, cd, sinEterm, cosEterm; dms raTemp, raDelta, decDelta; Dec.SinCos(sd, cd); raTemp.setH(RA.Hours() + 11.25); raTemp.SinCos(sinEterm, cosEterm); raDelta.setH(0.0227 * sinEterm / (3600. * cd)); decDelta.setD(0.341 * cosEterm * sd / 3600. + 0.029 * cd / 3600.); return SkyPoint(raDelta, decDelta); } void SkyPoint::addEterms(void) { SkyPoint spd = Eterms(); RA = RA + spd.ra(); Dec = Dec + spd.dec(); } void SkyPoint::subtractEterms(void) { SkyPoint spd = Eterms(); RA = RA - spd.ra(); Dec = Dec - spd.dec(); } dms SkyPoint::angularDistanceTo(const SkyPoint *sp, double *const positionAngle) const { // double dalpha = sp->ra().radians() - ra().radians() ; // double ddelta = sp->dec().radians() - dec().radians(); CachingDms dalpha = sp->ra() - ra(); CachingDms ddelta = sp->dec() - dec(); // double sa = sin(dalpha/2.); // double sd = sin(ddelta/2.); // double hava = sa*sa; // double havd = sd*sd; // Compute the haversin directly: double hava = (1 - dalpha.cos()) / 2.; double havd = (1 - ddelta.cos()) / 2.; // Haversine law double aux = havd + (sp->dec().cos()) * dec().cos() * hava; dms angDist; angDist.setRadians(2. * fabs(asin(sqrt(aux)))); if (positionAngle) { // Also compute the position angle of the line from this SkyPoint to sp //*positionAngle = acos( tan(-ddelta)/tan( angDist.radians() ) ); // FIXME: Might fail for large ddelta / zero angDist //if( -dalpha < 0 ) // *positionAngle = 2*M_PI - *positionAngle; *positionAngle = atan2f(dalpha.sin(), (dec().cos()) * tan(sp->dec().radians()) - (dec().sin()) * dalpha.cos()) * 180 / M_PI; } return angDist; } double SkyPoint::vRSun(long double jd0) { double ca, sa, cd, sd, vsun; double cosRA, sinRA, cosDec, sinDec; /* Sun apex (where the sun goes) coordinates */ dms asun(270.9592); // Right ascention: 18h 3m 50.2s [J2000] dms dsun(30.00467); // Declination: 30^o 0' 16.8'' [J2000] vsun = 20.; // [km/s] asun.SinCos(sa, ca); dsun.SinCos(sd, cd); /* We need an auxiliary SkyPoint since we need the * source referred to the J2000 equinox and we do not want to ovewrite * the current values */ SkyPoint aux; aux.set(RA0, Dec0); aux.precessFromAnyEpoch(jd0, J2000); aux.ra().SinCos(sinRA, cosRA); aux.dec().SinCos(sinDec, cosDec); /* Computation is done performing the scalar product of a unitary vector in the direction of the source with the vector velocity of Sun, both being in the LSR reference system: Vlsr = Vhel + Vsun.u_radial => Vlsr = Vhel + vsun(cos D cos A,cos D sen A,sen D).(cos d cos a,cos d sen a,sen d) Vhel = Vlsr - Vsun.u_radial */ return vsun * (cd * cosDec * (cosRA * ca + sa * sinRA) + sd * sinDec); } double SkyPoint::vHeliocentric(double vlsr, long double jd0) { return vlsr - vRSun(jd0); } double SkyPoint::vHelioToVlsr(double vhelio, long double jd0) { return vhelio + vRSun(jd0); } double SkyPoint::vREarth(long double jd0) { double sinRA, sinDec, cosRA, cosDec; /* u_radial = unitary vector in the direction of the source Vlsr = Vhel + Vsun.u_radial = Vgeo + VEarth.u_radial + Vsun.u_radial => Vgeo = (Vlsr -Vsun.u_radial) - VEarth.u_radial = Vhel - VEarth.u_radial = Vhel - (vx, vy, vz).(cos d cos a,cos d sen a,sen d) */ /* We need an auxiliary SkyPoint since we need the * source referred to the J2000 equinox and we do not want to ovewrite * the current values */ SkyPoint aux(RA0, Dec0); aux.precessFromAnyEpoch(jd0, J2000); aux.ra().SinCos(sinRA, cosRA); aux.dec().SinCos(sinDec, cosDec); /* vEarth is referred to the J2000 equinox, hence we need that the source coordinates are also in the same reference system. */ KSNumbers num(jd0); return num.vEarth(0) * cosDec * cosRA + num.vEarth(1) * cosDec * sinRA + num.vEarth(2) * sinDec; } double SkyPoint::vGeocentric(double vhelio, long double jd0) { return vhelio - vREarth(jd0); } double SkyPoint::vGeoToVHelio(double vgeo, long double jd0) { return vgeo + vREarth(jd0); } double SkyPoint::vRSite(double vsite[3]) { double sinRA, sinDec, cosRA, cosDec; RA.SinCos(sinRA, cosRA); Dec.SinCos(sinDec, cosDec); return vsite[0] * cosDec * cosRA + vsite[1] * cosDec * sinRA + vsite[2] * sinDec; } double SkyPoint::vTopoToVGeo(double vtopo, double vsite[3]) { return vtopo + vRSite(vsite); } double SkyPoint::vTopocentric(double vgeo, double vsite[3]) { return vgeo - vRSite(vsite); } bool SkyPoint::checkCircumpolar(const dms *gLat) const { return fabs(dec().Degrees()) > (90 - fabs(gLat->Degrees())); } dms SkyPoint::altRefracted() const { if (Options::useRefraction()) return refract(Alt); else return Alt; } double SkyPoint::refractionCorr(double alt) { return 1.02 / tan(dms::DegToRad * (alt + 10.3 / (alt + 5.11))) / 60; } double SkyPoint::refract(const double alt) { static double corrCrit = SkyPoint::refractionCorr(SkyPoint::altCrit); if (alt > SkyPoint::altCrit) return (alt + SkyPoint::refractionCorr(alt)); else return (alt + corrCrit * (alt + 90) / (SkyPoint::altCrit + 90)); // Linear extrapolation from corrCrit at altCrit to 0 at -90 degrees } // Found uncorrected value by solving equation. This is OK since // unrefract is never called in loops. // // Convergence is quite fast just a few iterations. double SkyPoint::unrefract(const double alt) { double h0 = alt; double h1 = alt - (refract(h0) - h0); // It's probably okay to add h0 in refract() and subtract it here, since refract() is called way more frequently. while (fabs(h1 - h0) > 1e-4) { h0 = h1; h1 = alt - (refract(h0) - h0); } return h1; } dms SkyPoint::findAltitude(const SkyPoint *p, const KStarsDateTime &dt, const GeoLocation *geo, const double hour) { Q_ASSERT(p); if (!p) return dms(NaN::d); // Jasem 2015-08-24 Using correct procedure to find altitude return SkyPoint::timeTransformed(p, dt, geo, hour).alt(); } SkyPoint SkyPoint::timeTransformed(const SkyPoint *p, const KStarsDateTime &dt, const GeoLocation *geo, const double hour) { Q_ASSERT(p); if (!p) return SkyPoint(NaN::d, NaN::d); // Jasem 2015-08-24 Using correct procedure to find altitude SkyPoint sp = *p; // make a copy KStarsDateTime targetDateTime = dt.addSecs(hour * 3600.0); dms LST = geo->GSTtoLST(targetDateTime.gst()); sp.EquatorialToHorizontal(&LST, geo->lat()); return sp; } double SkyPoint::maxAlt(const dms &lat) const { double retval = (lat.Degrees() + 90. - dec().Degrees()); if (retval > 90.) retval = 180. - retval; return retval; } double SkyPoint::minAlt(const dms &lat) const { double retval = (lat.Degrees() - 90. + dec().Degrees()); if (retval < -90.) retval = 180. + retval; return retval; } diff --git a/kstars/skyobjects/skypoint.h b/kstars/skyobjects/skypoint.h index 6fce57359..b6ff4ebc6 100644 --- a/kstars/skyobjects/skypoint.h +++ b/kstars/skyobjects/skypoint.h @@ -1,580 +1,580 @@ /*************************************************************************** skypoint.h - K Desktop Planetarium ------------------- begin : Sun Feb 11 2001 copyright : (C) 2001-2005 by Jason Harris email : jharris@30doradus.org copyright : (C) 2004-2005 by Pablo de Vicente email : p.devicente@wanadoo.es ***************************************************************************/ /*************************************************************************** * * * 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 "cachingdms.h" #include "kstarsdatetime.h" #include //#define PROFILE_COORDINATE_CONVERSION class KSNumbers; class KSSun; class GeoLocation; /** @class SkyPoint * *The sky coordinates of a point in the sky. The *coordinates are stored in both Equatorial (Right Ascension, *Declination) and Horizontal (Azimuth, Altitude) coordinate systems. *Provides set/get functions for each coordinate angle, and functions *to convert between the Equatorial and Horizon coordinate systems. * *Because the coordinate values change slowly over time (due to *precession, nutation), the "catalog coordinates" are stored *(RA0, Dec0), which were the true coordinates on Jan 1, 2000. *The true coordinates (RA, Dec) at any other epoch can be found *from the catalog coordinates using updateCoords(). *@short Stores dms coordinates for a point in the sky. *for converting between coordinate systems. *@author Jason Harris *@version 1.0 */ class SkyPoint { public: /** Default constructor: Sets RA, Dec and RA0, Dec0 according *to arguments. Does not set Altitude or Azimuth. *@param r Right Ascension *@param d Declination */ SkyPoint(const dms &r, const dms &d) : RA0(r), Dec0(d), RA(r), Dec(d), lastPrecessJD(J2000) {} SkyPoint(const CachingDms &r, const CachingDms &d) : RA0(r), Dec0(d), RA(r), Dec(d), lastPrecessJD(J2000) {} /** Alternate constructor using double arguments, for convenience. *It behaves essentially like the default constructor. *@param r Right Ascension, expressed as a double *@param d Declination, expressed as a double *@note This also sets RA0 and Dec0 */ //FIXME: this (*15.0) thing is somewhat hacky. explicit SkyPoint(double r, double d) : RA0(r * 15.0), Dec0(d), RA(r * 15.0), Dec(d), lastPrecessJD(J2000) {} /** *@short Default constructor. Sets nonsense values for RA, Dec etc */ SkyPoint(); virtual ~SkyPoint(); //// //// 1. Setting Coordinates //// ======================= /** * @short Sets RA, Dec and RA0, Dec0 according to arguments. * Does not set Altitude or Azimuth. * @param r Right Ascension * @param d Declination * @note This function also sets RA0 and Dec0 to the same values, so call at your own peril! * @note FIXME: This method must be removed, or an epoch argument must be added. */ void set(const dms &r, const dms &d); /** Sets RA0, the catalog Right Ascension. *@param r catalog Right Ascension. */ inline void setRA0(dms r) { RA0 = r; } inline void setRA0(CachingDms r) { RA0 = r; } /** Overloaded member function, provided for convenience. *It behaves essentially like the above function. *@param r Right Ascension, expressed as a double. */ inline void setRA0(double r) { RA0.setH(r); } /** Sets Dec0, the catalog Declination. *@param d catalog Declination. */ inline void setDec0(dms d) { Dec0 = d; } inline void setDec0(const CachingDms &d) { Dec0 = d; } /** Overloaded member function, provided for convenience. *It behaves essentially like the above function. *@param d Declination, expressed as a double. */ inline void setDec0(double d) { Dec0.setD(d); } /** Sets RA, the current Right Ascension. *@param r Right Ascension. */ inline void setRA(dms& r) { RA = r; } inline void setRA(const CachingDms &r) { RA = r; } /** Overloaded member function, provided for convenience. *It behaves essentially like the above function. *@param r Right Ascension, expressed as a double. */ inline void setRA(double r) { RA.setH(r); } /** Sets Dec, the current Declination *@param d Declination. */ inline void setDec(dms d) { Dec = d; } inline void setDec(const CachingDms &d) { Dec = d; } /** Overloaded member function, provided for convenience. *It behaves essentially like the above function. *@param d Declination, expressed as a double. */ inline void setDec(double d) { Dec.setD(d); } /** Sets Alt, the Altitude. *@param alt Altitude. */ inline void setAlt(dms alt) { Alt = alt; } /** Overloaded member function, provided for convenience. *It behaves essentially like the above function. *@param alt Altitude, expressed as a double. */ inline void setAlt(double alt) { Alt.setD(alt); } /** Sets Az, the Azimuth. *@param az Azimuth. */ inline void setAz(dms az) { Az = az; } /** Overloaded member function, provided for convenience. *It behaves essentially like the above function. *@param az Azimuth, expressed as a double. */ inline void setAz(double az) { Az.setD(az); } //// //// 2. Returning coordinates. //// ========================= /** @return a pointer to the catalog Right Ascension. */ inline const CachingDms &ra0() const { return RA0; } /** @return a pointer to the catalog Declination. */ inline const CachingDms &dec0() const { return Dec0; } /** @returns a pointer to the current Right Ascension. */ inline const CachingDms &ra() const { return RA; } /** @return a pointer to the current Declination. */ inline const CachingDms &dec() const { return Dec; } /** @return a pointer to the current Azimuth. */ inline const dms &az() const { return Az; } /** @return a pointer to the current Altitude. */ inline const dms &alt() const { return Alt; } /** @return refracted altitude. This function uses * Options::useRefraction to determine whether refraction * correction should be applied */ dms altRefracted() const; /** * @return the JD for the precessed coordinates */ inline double getLastPrecessJD() const { return lastPrecessJD; } /** * @return the airmass of the point. Convenience method. * @note Question: is it better to use alt or refracted alt? Minor difference, probably doesn't matter. */ inline double airmass() const { return 1. / sin(alt().radians()); } //// //// 3. Coordinate conversions. //// ========================== /** Determine the (Altitude, Azimuth) coordinates of the *SkyPoint from its (RA, Dec) coordinates, given the local *sidereal time and the observer's latitude. *@param LST pointer to the local sidereal time *@param lat pointer to the geographic latitude */ void EquatorialToHorizontal(const CachingDms *LST, const CachingDms *lat); // Deprecated method provided for compatibility void EquatorialToHorizontal(const dms *LST, const dms *lat); /** Determine the (RA, Dec) coordinates of the *SkyPoint from its (Altitude, Azimuth) coordinates, given the local *sidereal time and the observer's latitude. *@param LST pointer to the local sidereal time *@param lat pointer to the geographic latitude */ void HorizontalToEquatorial(const dms *LST, const dms *lat); /** Determine the Ecliptic coordinates of the SkyPoint, given the Julian Date. *The ecliptic coordinates are returned as reference arguments (since *they are not stored internally) */ void findEcliptic(const CachingDms *Obliquity, dms &EcLong, dms &EcLat); /** Set the current (RA, Dec) coordinates of the *SkyPoint, given pointers to its Ecliptic (Long, Lat) coordinates, and *to the current obliquity angle (the angle between the equator and ecliptic). */ void setFromEcliptic(const CachingDms *Obliquity, const dms &EcLong, const dms &EcLat); /** Computes galactic coordinates from equatorial coordinates referred to * epoch 1950. RA and Dec are, therefore assumed to be B1950 * coordinates. */ void Equatorial1950ToGalactic(dms &galLong, dms &galLat); /** Computes equatorial coordinates referred to 1950 from galactic ones referred to * epoch B1950. RA and Dec are, therefore assumed to be B1950 * coordinates. */ void GalacticToEquatorial1950(const dms *galLong, const dms *galLat); //// //// 4. Coordinate update/corrections. //// ================================= /** Determine the current coordinates (RA, Dec) from the catalog *coordinates (RA0, Dec0), accounting for both precession and nutation. *@param num pointer to KSNumbers object containing current values of time-dependent variables. *@param includePlanets does nothing in this implementation (see KSPlanetBase::updateCoords()). *@param lat does nothing in this implementation (see KSPlanetBase::updateCoords()). *@param LST does nothing in this implementation (see KSPlanetBase::updateCoords()). *@param forceRecompute reapplies precession, nutation and aberration even if the time passed since the last computation is not significant. */ - virtual void updateCoords(const KSNumbers *num, bool includePlanets = true, const CachingDms *lat = 0, - const CachingDms *LST = 0, bool forceRecompute = false); + virtual void updateCoords(const KSNumbers *num, bool includePlanets = true, const CachingDms *lat = nullptr, + const CachingDms *LST = nullptr, bool forceRecompute = false); /** * @brief updateCoordsNow Shortcut for updateCoords( const KSNumbers *num, false, nullptr, nullptr, true) * @param num pointer to KSNumbers object containing current values of time-dependent variables. */ void updateCoordsNow(const KSNumbers *num) { updateCoords(num, false, nullptr, nullptr, true); } /** Computes the apparent coordinates for this SkyPoint for any epoch, *accounting for the effects of precession, nutation, and aberration. *Similar to updateCoords(), but the starting epoch need not be *J2000, and the target epoch need not be the present time. *@param jd0 Julian Day which identifies the original epoch *@param jdf Julian Day which identifies the final epoch */ void apparentCoord(long double jd0, long double jdf); /** Determine the effects of nutation for this SkyPoint. *@param num pointer to KSNumbers object containing current values of *time-dependent variables. */ void nutate(const KSNumbers *num); /** *@short Check if this sky point is close enough to the sun for * gravitational lensing to be significant */ bool checkBendLight(); /** Correct for the effect of "bending" of light around the sun for * positions near the sun. * * General Relativity tells us that a photon with an impact * parameter b is deflected through an angle 1.75" (Rs / b) where * Rs is the solar radius. * * @return: true if the light was bent, false otherwise */ bool bendlight(); /** *@short Obtain a Skypoint with RA0 and Dec0 set from the RA, Dec * of this skypoint. Also set the RA0, Dec0 of this SkyPoint if not * set already and the target epoch is J2000. */ SkyPoint deprecess(const KSNumbers *num, long double epoch = J2000); /** Determine the effects of aberration for this SkyPoint. *@param num pointer to KSNumbers object containing current values of *time-dependent variables. */ void aberrate(const KSNumbers *num); /** General case of precession. It precess from an original epoch to a *final epoch. In this case RA0, and Dec0 from SkyPoint object represent *the coordinates for the original epoch and not for J2000, as usual. *@param jd0 Julian Day which identifies the original epoch *@param jdf Julian Day which identifies the final epoch */ void precessFromAnyEpoch(long double jd0, long double jdf); /** Determine the E-terms of aberration *In the past, the mean places of stars published in catalogs included *the contribution to the aberration due to the ellipticity of the orbit *of the Earth. These terms, known as E-terms were almost constant, and *in the newer catalogs (FK5) are not included. Therefore to convert from *FK4 to FK5 one has to compute these E-terms. */ SkyPoint Eterms(void); /** Exact precession from Besselian epoch 1950 to epoch J2000. The *coordinates referred to the first epoch are in the FK4 catalog, while the latter are in the Fk5 one. *Reference: Smith, C. A.; Kaplan, G. H.; Hughes, J. A.; Seidelmann, *P. K.; Yallop, B. D.; Hohenkerk, C. Y. *Astronomical Journal, vol. 97, Jan. 1989, p. 265-279 *This transformation requires 4 steps: * - Correct E-terms * - Precess from B1950 to 1984, January 1st, 0h, using Newcomb expressions * - Add zero point correction in right ascension for 1984 * - Precess from 1984, January 1st, 0h to J2000 */ void B1950ToJ2000(void); /** Exact precession from epoch J2000 Besselian epoch 1950. The coordinates *referred to the first epoch are in the FK4 catalog, while the *latter are in the Fk5 one. *Reference: Smith, C. A.; Kaplan, G. H.; Hughes, J. A.; Seidelmann, *P. K.; Yallop, B. D.; Hohenkerk, C. Y. *Astronomical Journal, vol. 97, Jan. 1989, p. 265-279 *This transformation requires 4 steps: * - Precess from J2000 to 1984, January 1st, 0h * - Add zero point correction in right ascension for 1984 * - Precess from 1984, January 1st, 0h, to B1950 using Newcomb expressions * - Correct E-terms */ void J2000ToB1950(void); /** Coordinates in the FK4 catalog include the effect of aberration due *to the ellipticity of the orbit of the Earth. Coordinates in the FK5 *catalog do not include these terms. In order to convert from B1950 (FK4) *to actual mean places one has to use this function. */ void addEterms(void); /** Coordinates in the FK4 catalog include the effect of aberration due *to the ellipticity of the orbit of the Earth. Coordinates in the FK5 *catalog do not include these terms. In order to convert from * FK5 coordinates to B1950 (FK4) one has to use this function. */ void subtractEterms(void); /** Computes the angular distance between two SkyObjects. The algorithm * to compute this distance is: * cos(distance) = sin(d1)*sin(d2) + cos(d1)*cos(d2)*cos(a1-a2) * where a1,d1 are the coordinates of the first object and a2,d2 are * the coordinates of the second object. * However this algorithm is not accurate when the angular separation * is small. * Meeus provides a different algorithm in page 111 which we * implement here. * @param sp SkyPoint to which distance is to be calculated * @param positionAngle if a non-null pointer is passed, the position angle [E of N] in degrees from this SkyPoint to sp is computed and stored in the passed variable. * @return dms angle representing angular separation. **/ - dms angularDistanceTo(const SkyPoint *sp, double *const positionAngle = 0) const; + dms angularDistanceTo(const SkyPoint *sp, double *const positionAngle = nullptr) const; /** * @return returns true if _current_ epoch RA / Dec match */ inline bool operator==(SkyPoint &p) const { return (ra() == p.ra() && dec() == p.dec()); } /** Computes the velocity of the Sun projected on the direction of the source. * * @param jd Epoch expressed as julian day to which the source coordinates refer to. * @return Radial velocity of the source referred to the barycenter of the solar system in km/s **/ double vRSun(long double jd); /** Computes the radial velocity of a source referred to the solar system barycenter * from the radial velocity referred to the * Local Standard of Rest, aka known as VLSR. To compute it we need the coordinates of the * source the VLSR and the epoch for the source coordinates. * * @param vlsr radial velocity of the source referred to the LSR in km/s * @param jd Epoch expressed as julian day to which the source coordinates refer to. * @return Radial velocity of the source referred to the barycenter of the solar system in km/s **/ double vHeliocentric(double vlsr, long double jd); /** Computes the radial velocity of a source referred to the Local Standard of Rest, also known as VLSR * from the radial velocity referred to the solar system barycenter * * @param vhelio radial velocity of the source referred to the LSR in km/s * @param jd Epoch expressed as julian day to which the source coordinates refer to. * @return Radial velocity of the source referred to the barycenter of the solar system in km/s **/ double vHelioToVlsr(double vhelio, long double jd); /** Computes the velocity of any object projected on the direction of the source. * @param jd0 Julian day for which we compute the direction of the source * @return velocity of the Earth projected on the direction of the source kms-1 */ double vREarth(long double jd0); /** Computes the radial velocity of a source referred to the center of the earth * from the radial velocity referred to the solar system barycenter * * @param vhelio radial velocity of the source referred to the barycenter of the * solar system in km/s * @param jd Epoch expressed as julian day to which the source coordinates refer to. * @return Radial velocity of the source referred to the center of the Earth in km/s **/ double vGeocentric(double vhelio, long double jd); /** Computes the radial velocity of a source referred to the solar system barycenter * from the velocity referred to the center of the earth * * @param vgeo radial velocity of the source referred to the center of the Earth * [km/s] * @param jd Epoch expressed as julian day to which the source coordinates refer to. * @return Radial velocity of the source referred to the solar system barycenter in km/s **/ double vGeoToVHelio(double vgeo, long double jd); /** Computes the velocity of any object (oberver's site) projected on the * direction of the source. * @param vsite velocity of that object in cartesian coordinates * @return velocity of the object projected on the direction of the source kms-1 */ double vRSite(double vsite[3]); /** Computes the radial velocity of a source referred to the observer site on the surface * of the earth from the geocentric velovity and the velocity of the site referred to the center * of the Earth. * * @param vgeo radial velocity of the source referred to the center of the earth in km/s * @param vsite Velocity at which the observer moves referred to the center of the earth. * @return Radial velocity of the source referred to the observer's site in km/s **/ double vTopocentric(double vgeo, double vsite[3]); /** Computes the radial velocity of a source referred to the center of the Earth from * the radial velocity referred to an observer site on the surface of the earth * * @param vtopo radial velocity of the source referred to the observer's site in km/s * @param vsite Velocity at which the observer moves referred to the center of the earth. * @return Radial velocity of the source referred the center of the earth in km/s **/ double vTopoToVGeo(double vtopo, double vsite[3]); /** Find the SkyPoint obtained by moving distance dist * (arcseconds) away from the givenSkyPoint * * @param dist Distance to move through in arcseconds * @param from The SkyPoint to move away from * @return a SkyPoint that is at the dist away from this SkyPoint in the direction away from */ SkyPoint moveAway(const SkyPoint &from, double dist) const; /** * @short Check if this point is circumpolar at the given geographic latitude */ bool checkCircumpolar(const dms *gLat) const; /** Calculate refraction correction. Parameter and return value are in degrees */ static double refractionCorr(double alt); /** * @short Apply refraction correction to altitude. * @param alt altitude to be corrected, in degrees * @return altitude after refraction correction, in degrees */ static double refract(const double alt); /** * @short Remove refraction correction. * @param alt altitude from which refraction correction must be removed, in degrees * @return altitude without refraction correction, in degrees */ static double unrefract(const double alt); /** * @short Apply refraction correction to altitude. Overloaded method using * dms provided for convenience * @see SkyPoint::refract( const double alt ) */ static inline dms refract(const dms alt) { return dms(refract(alt.Degrees())); } /** * @short Remove refraction correction. Overloaded method using * dms provided for convenience * @see SkyPoint::unrefract( const double alt ) */ static inline dms unrefract(const dms alt) { return dms(unrefract(alt.Degrees())); } /** * @short Compute the altitude of a given skypoint hour hours from the given date/time * @param p SkyPoint whose altitude is to be computed (const pointer, the method works on a clone) * @param dt Date/time that corresponds to 0 hour * @param geo GeoLocation object specifying the location * @param hour double specifying offset in hours from dt for which altitude is to be found * @return a dms containing (unrefracted?) altitude of the object at dt + hour hours at the given location * @note This method is used in multiple places across KStars * @todo Fix code duplication in AltVsTime and KSAlmanac by using this method instead! FIXME. */ static dms findAltitude(const SkyPoint *p, const KStarsDateTime &dt, const GeoLocation *geo, const double hour = 0); /** * @short returns a time-transformed SkyPoint. See SkyPoint::findAltitude() for details * @todo Fix this documentation. */ static SkyPoint timeTransformed(const SkyPoint *p, const KStarsDateTime &dt, const GeoLocation *geo, const double hour = 0); /** *@short Critical height for atmospheric refraction * corrections. Below this, the height formula produces meaningles * results and the correction value is just interpolated. */ static const double altCrit; /** * @short Return the object's altitude at the upper culmination for the given latitude * @return the maximum altitude in degrees */ double maxAlt(const dms &lat) const; /** * @short Return the object's altitude at the lower culmination for the given latitude * @return the minimum altitude in degrees */ double minAlt(const dms &lat) const; #ifdef PROFILE_COORDINATE_CONVERSION static double cpuTime_EqToHz; static long unsigned eqToHzCalls; #endif protected: /** * Precess this SkyPoint's catalog coordinates to the epoch described by the * given KSNumbers object. * @param num pointer to a KSNumbers object describing the target epoch. */ void precess(const KSNumbers *num); #ifdef UNIT_TEST friend class TestSkyPoint; // Test class #endif private: CachingDms RA0, Dec0; //catalog coordinates CachingDms RA, Dec; //current true sky coordinates dms Alt, Az; static KSSun *m_Sun; protected: double lastPrecessJD; // JD at which the last coordinate update (see updateCoords) for this SkyPoint was done }; diff --git a/kstars/skyobjects/starobject.h b/kstars/skyobjects/starobject.h index 9e22139fb..5e2da91a1 100644 --- a/kstars/skyobjects/starobject.h +++ b/kstars/skyobjects/starobject.h @@ -1,307 +1,307 @@ /*************************************************************************** starobject.h - K Desktop Planetarium ------------------- begin : Tue Sep 18 2001 copyright : (C) 2001 by Thomas Kabelmann email : tk78@gmx.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 //#define PROFILE_UPDATECOORDS #include "skyobject.h" #include struct DeepStarData; class KSNumbers; class KSPopupMenu; struct StarData; /** @class StarObject *This is a subclass of SkyObject. It adds the Spectral type, and flags *for variability and multiplicity. *For stars, the primary name (n) is the latin name (e.g., "Betelgeuse"). The *secondary name (n2) is the genetive name (e.g., "alpha Orionis"). *@short subclass of SkyObject specialized for stars. *@author Thomas Kabelmann *@version 1.0 */ class StarObject : public SkyObject { public: /** @short returns the reindex interval (in centuries!) for the given * magnitude of proper motion (in milliarcsec/year). ASSUMING a * 25 arc-minute margin for proper motion. */ static double reindexInterval(double pm); /** *Constructor. Sets sky coordinates, magnitude, latin name, genetive name, and *spectral type. *@param r Right Ascension *@param d Declination *@param m magnitude *@param n common name *@param n2 genetive name *@param sptype Spectral Type *@param pmra Proper motion in RA direction [mas/yr] *@param pmdec Proper motion in Dec direction [mas/yr] *@param par Parallax angle [mas] *@param mult Multiplicity flag (false=dingle star; true=multiple star) *@param var Variability flag (true if star is a known periodic variable) *@param hd Henry Draper Number */ explicit StarObject(dms r = dms(0.0), dms d = dms(0.0), float m = 0.0, const QString &n = QString(), const QString &n2 = QString(), const QString &sptype = "--", double pmra = 0.0, double pmdec = 0.0, double par = 0.0, bool mult = false, bool var = false, int hd = 0); /** *Constructor. Sets sky coordinates, magnitude, latin name, genetive name, and *spectral type. Differs from above function only in data type of RA and Dec. *@param r Right Ascension *@param d Declination *@param m magnitude *@param n common name *@param n2 genetive name *@param sptype Spectral Type *@param pmra Proper motion in RA direction [mas/yr] *@param pmdec Proper motion in Dec direction [mas/yr] *@param par Parallax angle [mas] *@param mult Multiplicity flag (false=dingle star; true=multiple star) *@param var Variability flag (true if star is a known periodic variable) *@param hd Henry Draper Number */ StarObject(double r, double d, float m = 0.0, const QString &n = QString(), const QString &n2 = QString(), const QString &sptype = "--", double pmra = 0.0, double pmdec = 0.0, double par = 0.0, bool mult = false, bool var = false, int hd = 0); StarObject *clone() const override; UID getUID() const override; /** Copy constructor */ StarObject(const StarObject &o); /** Destructor. (Empty) */ ~StarObject() override {} /** *@short Initializes a StarObject to given data * * This is almost like the StarObject constructor itself, but it avoids * setting up name, gname etc for unnamed stars. If called instead of the * constructor, this method will be much faster for unnamed stars * *@param stardata Pointer to starData object containing required data (except name and gname) *@return Nothing */ void init(const StarData *stardata); /** *@short Initializes a StarObject to given data * *@param stardata Pointer to deepStarData object containing the available data *@return Nothing */ void init(const DeepStarData *stardata); /** *@short Sets the name, genetive name, and long name * *@param name Common name *@param name2 Genetive name */ void setNames(const QString &name, const QString &name2); /** @return true if the star has a name ("star" doesn't count) */ inline bool hasName() const { return (!Name.isEmpty() && Name != starString); } /** @return true if the star has a latin name ("star" or HD... doesn't count) */ inline bool hasLatinName() const { return (!Name.isEmpty() && Name != starString && Name != gname(false) && Name != gname(true) && !Name.startsWith("HD ")); } /** If star is unnamed return "star" otherwise return the name */ inline QString name(void) const override { return hasName() ? Name : starString; } /** If star is unnamed return "star" otherwise return the longname */ inline QString longname(void) const override { return hasLongName() ? LongName : starString; } /** Returns entire spectral type string * @return Spectral Type string */ QString sptype(void) const; /** Returns just the first character of the spectral type string. */ char spchar() const; /** Returns the genetive name of the star. * @return genetive name of the star */ QString gname(bool useGreekChars = true) const; /** Returns the greek letter portion of the star's genetive name. * Returns empty string if star has no genetive name defined. * @return greek letter portion of genetive name */ QString greekLetter(bool useGreekChars = true) const; /** @return the genitive form of the star's constellation. */ QString constell(void) const; /** Determine the current coordinates (RA, Dec) from the catalog * coordinates (RA0, Dec0), accounting for both precession and nutation. * @param num pointer to KSNumbers object containing current values of * time-dependent variables. * @param includePlanets does nothing in this implementation (see KSPlanetBase::updateCoords()). * @param lat does nothing in this implementation (see KSPlanetBase::updateCoords()). * @param LST does nothing in this implementation (see KSPlanetBase::updateCoords()). */ - void updateCoords(const KSNumbers *num, bool includePlanets = true, const CachingDms *lat = 0, - const CachingDms *LST = 0, bool forceRecompute = false) override; + void updateCoords(const KSNumbers *num, bool includePlanets = true, const CachingDms *lat = nullptr, + const CachingDms *LST = nullptr, bool forceRecompute = false) override; /** @short fills ra and dec with the coordinates of the star with the proper * motion correction but without precesion and its friends. It is used * in StarComponent to re-index all the stars. * * @return true if we changed the coordinates, false otherwise * NOTE: ra and dec both in degrees. */ bool getIndexCoords(const KSNumbers *num, CachingDms &ra, CachingDms &dec); bool getIndexCoords(const KSNumbers *num, double *ra, double *dec); /** @short added for JIT updates from both StarComponent and ConstellationLines */ void JITupdate(); /** @short returns the magnitude of the proper motion correction in milliarcsec/year */ inline double pmMagnitude() const { double cosDec = dec0().cos(); return sqrt(cosDec * cosDec * pmRA() * pmRA() + pmDec() * pmDec()); } /** *@short returns the square of the magnitude of the proper motion correction in (milliarcsec/year)^2 *@note This method is faster when the square root need not be taken */ inline double pmMagnitudeSquared() const { double metric_weighted_pmRA = dec0().cos() * pmRA(); return (metric_weighted_pmRA * metric_weighted_pmRA + pmDec() * pmDec()); } /** @short Set the Ra and Dec components of the star's proper motion, in milliarcsec/year. * Note that the RA component is multiplied by cos(dec). * @param pmra the new RA propoer motion * @param pmdec the new Dec proper motion */ inline void setProperMotion(double pmra, double pmdec) { PM_RA = pmra; PM_Dec = pmdec; } /** @return the RA component of the star's proper motion, in mas/yr (multiplied by cos(dec)) */ inline double pmRA() const { return PM_RA; } /** @return the Dec component of the star's proper motion, in mas/yr */ inline double pmDec() const { return PM_Dec; } /** @short set the star's parallax angle, in milliarcsec */ inline void setParallax(double plx) { Parallax = plx; } /** @return the star's parallax angle, in milliarcsec */ inline double parallax() const { return Parallax; } /** @return the star's distance from the Sun in parsecs, as computed from the parallax. */ inline double distance() const { return 1000. / parallax(); } /** @short set the star's multiplicity flag (i.e., is it a binary or multiple star?) * @param m true if binary/multiple star system */ inline void setMultiple(bool m) { Multiplicity = m; } /** @return whether the star is a binary or multiple starobject */ inline bool isMultiple() const { return Multiplicity; } /** @return the star's HD index */ inline int getHDIndex() const { return HD; } /** @short set the star's variability flag *@param v true if star is variable */ inline void setVariable(bool v) { Variability = v; } /** @return whether the star is a binary or multiple starobject */ inline bool isVariable() const { return Variability; } /** @short returns the name, the magnitude or both. */ QString nameLabel(bool drawName, bool drawMag) const; QString labelString() const override; /** *@return the pixel distance for offseting the star's name label *This takes the zoom level and the star's brightness into account. */ double labelOffset() const override; /** *@return the Visual magnitude of the star */ inline float getVMag() const { return V; } /** *@return the blue magnitude of the star */ inline float getBMag() const { return B; } /** *@return the B - V color index of the star, or a nonsense number *larger than 30 if it's not well defined */ inline float getBVIndex() const { return ((B < 30.0 && V < 30.0) ? B - V : 99.9); } void initPopupMenu(KSPopupMenu *pmenu) override; quint64 updateID; quint64 updateNumID; #ifdef PROFILE_UPDATECOORDS static double updateCoordsCpuTime; static unsigned int starsUpdated; #endif protected: // DEBUG EDIT. For testing proper motion, uncomment this, and related blocks // See starobject.cpp for further info. // static QVector Trail; // bool testStar; // END DEBUG private: double PM_RA { 0 }; double PM_Dec { 0 }; double Parallax { 0 }; bool Multiplicity { false }; bool Variability { false }; char SpType[2]; int HD { 0 }; // B and V magnitudes, separately. NOTE 1) This is kept separate from mag for a reason. // See init( const DeepStarData *); 2) This applies only to deep stars at the moment float B { 0 }; float V { 0 }; }; diff --git a/kstars/skyobjects/trailobject.cpp b/kstars/skyobjects/trailobject.cpp index 9f3d95c95..b62b995f0 100644 --- a/kstars/skyobjects/trailobject.cpp +++ b/kstars/skyobjects/trailobject.cpp @@ -1,144 +1,144 @@ /*************************************************************************** trailobject.cpp - K Desktop Planetarium ------------------- begin : Sat Oct 27 2007 copyright : (C) 2007 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 "trailobject.h" #include "kstarsdata.h" #include "skymap.h" #ifndef KSTARS_LITE #include "kspopupmenu.h" #endif #include "skycomponents/skylabeler.h" #include "skypainter.h" #include "projections/projector.h" #include "Options.h" #include #include QSet TrailObject::trailObjects; TrailObject::TrailObject(int t, dms r, dms d, float m, const QString &n) : SkyObject(t, r, d, m, n) { } TrailObject::TrailObject(int t, double r, double d, float m, const QString &n) : SkyObject(t, r, d, m, n) { } TrailObject::~TrailObject() { trailObjects.remove(this); } TrailObject *TrailObject::clone() const { Q_ASSERT(typeid(this) == typeid(static_cast(this))); // Ensure we are not slicing a derived class return new TrailObject(*this); } void TrailObject::updateTrail(dms *LST, const dms *lat) { for (int i = 0; i < Trail.size(); ++i) Trail[i].EquatorialToHorizontal(LST, lat); } void TrailObject::initPopupMenu(KSPopupMenu *pmenu) { #ifndef KSTARS_LITE pmenu->createPlanetMenu(this); #else Q_UNUSED(pmenu); #endif } void TrailObject::addToTrail(const QString &label) { Trail.append(SkyPoint(*this)); m_TrailLabels.append(label); trailObjects.insert(this); } void TrailObject::clipTrail() { if (Trail.size()) { Trail.removeFirst(); Q_ASSERT(m_TrailLabels.size()); m_TrailLabels.removeFirst(); } if (Trail.size()) // Eh? Shouldn't this be if( !Trail.size() ) -- asimha trailObjects.remove(this); } void TrailObject::clearTrail() { Trail.clear(); m_TrailLabels.clear(); trailObjects.remove(this); } void TrailObject::clearTrailsExcept(SkyObject *o) { - TrailObject *keep = 0; + TrailObject *keep = nullptr; foreach (TrailObject *tr, trailObjects) { if (tr != o) tr->clearTrail(); else keep = tr; } trailObjects = QSet(); if (keep) trailObjects.insert(keep); } void TrailObject::drawTrail(SkyPainter *skyp) const { Q_UNUSED(skyp) #ifndef KSTARS_LITE if (!Trail.size()) return; KStarsData *data = KStarsData::Instance(); QColor tcolor = QColor(data->colorScheme()->colorNamed("PlanetTrailColor")); skyp->setPen(QPen(tcolor, 1)); SkyLabeler *labeler = SkyLabeler::Instance(); labeler->setPen(tcolor); int n = Trail.size(); for (int i = 1; i < n; ++i) { if (Options::fadePlanetTrails()) { tcolor.setAlphaF(static_cast(i) / static_cast(n)); skyp->setPen(QPen(tcolor, 1)); } SkyPoint a = Trail[i - 1]; SkyPoint b = Trail[i]; skyp->drawSkyLine(&a, &b); if (i % 5 == 1) // TODO: Make drawing of labels configurable, incl. frequency etc. { QPointF pt = SkyMap::Instance()->projector()->toScreen(&a); labeler->drawGuideLabel(pt, m_TrailLabels[i - 1], 0.0); } } #endif } diff --git a/kstars/skypainter.h b/kstars/skypainter.h index 60037be26..edaf8ae34 100644 --- a/kstars/skypainter.h +++ b/kstars/skypainter.h @@ -1,185 +1,185 @@ /* SkyPainter: class for painting onto the sky for KStars 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. */ #pragma once #include #include #include "skycomponents/typedef.h" class ConstellationsArt; class DeepSkyObject; class KSComet; class KSPlanetBase; class LineList; class LineListLabel; class Satellite; class SkipHashList; class SkyMap; class SkyObject; class SkyPoint; class Supernova; /** * @short Draws things on the sky, without regard to backend. * This class serves as an interface to draw objects onto the sky without * worrying about whether we are using a QPainter or OpenGL. */ class SkyPainter { public: /** @short Constructor. */ SkyPainter(); /** @short Destructor */ virtual ~SkyPainter(); /** @short Set the pen of the painter **/ virtual void setPen(const QPen &pen) = 0; /** @short Set the brush of the painter **/ virtual void setBrush(const QBrush &brush) = 0; //FIXME: find a better way to do this. void setSizeMagLimit(float sizeMagLim); /** * Begin painting. * @note this function must be called before painting anything. * @see end() */ virtual void begin() = 0; /** * End and finalize painting. * @note this function must be called after painting anything. * @note it is not guaranteed that anything will actually be drawn until end() is called. * @see begin(); */ virtual void end() = 0; //////////////////////////////////// // // // SKY DRAWING FUNCTIONS: // // // //////////////////////////////////// /** @short Draw the sky background */ virtual void drawSkyBackground() = 0; /** * @short Draw a line between points in the sky. * @param a the first point * @param b the second point * @note this function will skip lines not on screen and clip lines * that are only partially visible. */ virtual void drawSkyLine(SkyPoint *a, SkyPoint *b) = 0; /** * @short Draw a polyline in the sky. * @param list a list of points in the sky * @param skipList a SkipList object used to control skipping line segments * @param label a pointer to the label for this line * @note it's more efficient to use this than repeated calls to drawSkyLine(), * because it avoids an extra points->size() -2 projections. */ - virtual void drawSkyPolyline(LineList *list, SkipHashList *skipList = 0, LineListLabel *label = 0) = 0; + virtual void drawSkyPolyline(LineList *list, SkipHashList *skipList = nullptr, LineListLabel *label = nullptr) = 0; /** * @short Draw a polygon in the sky. * @param list a list of points in the sky * @param forceClip If true (default), it enforces clipping of the polygon, otherwise, it draws the * complete polygen without running any boundary checks. * @see drawSkyPolyline() */ virtual void drawSkyPolygon(LineList *list, bool forceClip = true) = 0; /** * @short Draw a comet in the sky. * @param com comet to draw * @return true if a comet was drawn */ virtual bool drawComet(KSComet *com) = 0; /** * @short Draw a point source (e.g., a star). * @param loc the location of the source in the sky * @param mag the magnitude of the source * @param sp the spectral class of the source * @return true if a source was drawn */ virtual bool drawPointSource(SkyPoint *loc, float mag, char sp = 'A') = 0; /** * @short Draw a deep sky object * @param obj the object to draw * @param drawImage if true, try to draw the image of the object * @return true if it was drawn */ virtual bool drawDeepSkyObject(DeepSkyObject *obj, bool drawImage = false) = 0; /** * @short Draw a planet * @param planet the planet to draw * @return true if it was drawn */ virtual bool drawPlanet(KSPlanetBase *planet) = 0; /** * @short Draw the symbols for the observing list * @param obs the oberving list */ virtual void drawObservingList(const QList &obs) = 0; /** @short Draw flags */ virtual void drawFlags() = 0; /** @short Draw a satellite */ virtual bool drawSatellite(Satellite *sat) = 0; /** @short Draw a Supernova */ virtual bool drawSupernova(Supernova *sup) = 0; - virtual void drawHorizon(bool filled, SkyPoint *labelPoint = 0, bool *drawLabel = 0) = 0; + virtual void drawHorizon(bool filled, SkyPoint *labelPoint = nullptr, bool *drawLabel = nullptr) = 0; /** @short Get the width of a star of magnitude mag */ float starWidth(float mag) const; /** * @short Draw a ConstellationsArt object * @param obj the object to draw * @return true if it was drawn */ virtual bool drawConstellationArtImage(ConstellationsArt *obj) = 0; /** * @brief drawHips Draw HIPS all sky catalog * @return true if it was drawn */ virtual bool drawHips() = 0; protected: SkyMap *m_sm { nullptr }; private: float m_sizeMagLim; }; diff --git a/kstars/texturemanager.h b/kstars/texturemanager.h index 6704557b4..42475d5b0 100644 --- a/kstars/texturemanager.h +++ b/kstars/texturemanager.h @@ -1,76 +1,76 @@ /* 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. */ #pragma once #include #include #include class QGLWidget; class QImage; /** * @brief a singleton class to manage texture loading/retrieval */ class TextureManager : public QObject { Q_OBJECT public: /** @short Create the instance of TextureManager */ static TextureManager *Create(); /** @short Release the instance of TextureManager */ static void Release(); /** * Return texture image. If image is not found in cache tries to * load it from disk if that fails too returns reference to empty image. */ static const QImage &getImage(const QString &name); #ifdef HAVE_OPENGL /** * Bind OpenGL texture. Acts similarly to getImage but does * nothing if image is not found in the end */ static void bindTexture(const QString &name, QGLWidget *cxt); /** Bind OpenGL texture using QImage as source */ static void bindFromImage(const QImage &image, QGLWidget *cxt); #endif private: /** Shorthand for iterator to hashtable */ typedef QHash::const_iterator CacheIter; /** Private constructor */ - explicit TextureManager(QObject *parent = 0); + explicit TextureManager(QObject *parent = nullptr); /** Try find image in the cache and then to load it from disk if it's not found */ static CacheIter findTexture(const QString &name); // Pointer to singleton instance static TextureManager *m_p; // List of named textures QHash m_textures; // Prohibit copying TextureManager(const TextureManager &); TextureManager &operator=(const TextureManager &); }; diff --git a/kstars/time/simclock.h b/kstars/time/simclock.h index 4c8c5813c..27def1218 100644 --- a/kstars/time/simclock.h +++ b/kstars/time/simclock.h @@ -1,151 +1,151 @@ /*************************************************************************** simclock.h - description ------------------- begin : Mon Feb 18 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 "kstarsdatetime.h" #ifndef KSTARS_LITE #include #endif #include #include /** @class SimClock *@short kstars simulation clock *@author Mark Hollomon *@version 1.0 */ class SimClock : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kstars.SimClock") public: /** * Constructor * @param parent parent object for the clock * @param when the date/time to which the SimClock should be initialized in UTC */ - explicit SimClock(QObject *parent = 0, const KStarsDateTime &when = KStarsDateTime::currentDateTimeUtc()); + explicit SimClock(QObject *parent = nullptr, const KStarsDateTime &when = KStarsDateTime::currentDateTimeUtc()); /** @return const reference to the current simulation Universal Time. */ const KStarsDateTime &utc() const { return UTC; } /** Whether the clock is active or not is a bit complicated by the *introduction of "manual mode". In manual mode, SimClock's internal timer *is stopped, because the clock is ticked manually when the current update *has finished. So, if ManualMode is true, then isActive() checks *whether ManualActive is true. Otherwise, it checks whether the timer is *running. *@returns true if the Simulation clock is actively running. */ Q_INVOKABLE bool isActive(); /** @returns the current timestep setting */ double scale() const { return Scale; } /** Manual Mode is a new (04/2002) addition to the SimClock. It is *intended to be activated for large timesteps, when we want each frame *drawn to the screen to be precisely Scale seconds later than the *previous frame. (i.e., if the timescale is 1 year, then each successive *frame should be 1 year later than the previous frame). ManualMode *accomplishes this by stopping the internal timer and allowing the clock *to be advanced manually (the manualTick() slot is called at the end of each *KStars::updateTime()). *@returns whether Manual Mode is active. */ bool isManualMode() const { return ManualMode; } /**Sets Manual Mode on/off according to the bool argument. */ void setManualMode(bool on = true); public Q_SLOTS: #ifndef KSTARS_LITE /** DBUS function to stop the SimClock. */ Q_SCRIPTABLE Q_NOREPLY void stop(); /** DBUS function to start the SimClock. */ Q_SCRIPTABLE Q_NOREPLY void start(); /** DBUS function to set the time of the SimClock. */ Q_SCRIPTABLE Q_NOREPLY void setUTC(const KStarsDateTime &newtime); /** DBUS function to set scale of simclock. */ Q_SCRIPTABLE Q_NOREPLY void setClockScale(float s); #else // Define non-DBUS versions of functions above for use within KStarsLite /** Function to stop the SimClock. */ void stop(); /** Function to start the SimClock. */ void start(); /** Function to set the time of the SimClock. */ void setUTC(const KStarsDateTime &newtime); /** Function to set scale of simclock. */ void setClockScale(float s); #endif /** Respond to the QTimer::timeout signal */ void tick(); /** Equivalent of tick() for manual mode. * If ManualActive is true, add Scale seconds to the SimClock time. * (we may want to modify this slightly...e.g., the number of seconds in a * year is not constant (leap years), so it is better to increment the * year, instead of adding 31 million seconds. * set backward to true to reverse sign of Scale value */ void manualTick(bool force = false, bool backward = false); signals: /** The time has changed (emitted by setUTC() ) */ void timeChanged(); /** The clock has ticked (emitted by tick() )*/ void timeAdvanced(); /** The timestep has changed*/ void scaleChanged(float); /** This is an signal that is called on either clock start or clock stop with an appropriate boolean argument. Required so that we can bind it to KToggleAction::slotToggled(bool) */ void clockToggled(bool); private: long double julianmark { 0 }; KStarsDateTime UTC; QTimer tmr; double Scale { 1 }; QTime sysmark; int lastelapsed { 0 }; bool ManualMode { false }; bool ManualActive { false }; // used to generate names for dcop interfaces //static int idgen; // how often to update static int TimerInterval; // Disallow copying SimClock(const SimClock &); SimClock &operator=(const SimClock &); }; diff --git a/kstars/tools/adddeepskyobject.cpp b/kstars/tools/adddeepskyobject.cpp index ace18f8e9..0f6b0fcdc 100644 --- a/kstars/tools/adddeepskyobject.cpp +++ b/kstars/tools/adddeepskyobject.cpp @@ -1,391 +1,391 @@ /*************************************************************************** adddeepskyobject.cpp - K Desktop Planetarium ------------------- begin : Wed 17 Aug 2016 20:22:58 CDT copyright : (c) 2016 by Akarsh Simha email : akarsh.simha@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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 "adddeepskyobject.h" #include "syncedcatalogcomponent.h" #include "skyobjects/skyobject.h" #include #include #include AddDeepSkyObject::AddDeepSkyObject(QWidget *parent, SyncedCatalogComponent *catalog) : QDialog(parent), m_catalog(catalog), ui(new Ui::AddDeepSkyObject) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif Q_ASSERT(catalog); ui->setupUi(this); // Set up various things in the dialog so it is ready to be shown for (int k = 0; k < SkyObject::NUMBER_OF_KNOWN_TYPES; ++k) { ui->typeComboBox->addItem(SkyObject::typeName(k)); } ui->typeComboBox->addItem(SkyObject::typeName(SkyObject::TYPE_UNKNOWN)); ui->catalogNameEdit->setEnabled(false); ui->catalogNameEdit->setText(catalog->name()); ui->raInput->setDegType(false); resetView(); // Connections // connect(ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(slotOk())); QAbstractButton *resetButton = ui->buttonBox->button(QDialogButtonBox::Reset); connect(resetButton, SIGNAL(clicked()), this, SLOT(resetView())); connect(ui->fillFromTextButton, SIGNAL(clicked()), this, SLOT(slotFillFromText())); // Show the UI show(); } AddDeepSkyObject::~AddDeepSkyObject() { delete ui; } void AddDeepSkyObject::fillFromText(const QString &text) { // Parse text to fill in the options // TODO: Add code to match and guess object type, to match blue magnitude. QRegularExpression matchJ2000Line("^(.*)(?:J2000|ICRS|FK5|\\(2000(?:\\.0)?\\))(.*)$"); matchJ2000Line.setPatternOptions(QRegularExpression::MultilineOption); QRegularExpression matchCoords("(?:^|[^-\\d])([-+]?\\d\\d?)(?:h ?|d ?|[^\\d]?° ?|:| +)(\\d\\d)(?:m ?|\' ?|’ ?|′ " "?|:| +)(\\d\\d(?:\\.\\d+)?)?(?:s|\"|\'\'|”|″)?\\b"); QRegularExpression matchCoords2("J?\\d{6,6}[-+]\\d{6,6}"); QRegularExpression findMag1( "(?:[mM]ag(?:nitudes?)?\\s*(?:\\([vV]\\))?|V(?=\\b))(?:\\s*=|:)?\\s*(-?\\d{1,2}(?:\\.\\d{1,3})?)"); QRegularExpression findMag2("\\b-?\\d{1,2}(\\.\\d{1,3})?\\s*[mM]ag\\b"); QRegularExpression findSize1("\\b(\\d{1,3}(?:\\.\\d{1,2})?)\\s*(°|\'|\"|\'\')?\\s*[xX×]\\s*(\\d{1,3}(?:\\.\\d{1,2})" "?)\\s*(°|\'|\"|\'\')?\\b"); QRegularExpression findSize2("\\b(?:[Ss]ize|[Dd]imensions?|[Dd]iameter)[: " "](?:\\([vV]\\))?\\s*(\\d{1,3}(?:\\.\\d{1,2})?)\\s*(°|\'|\"|\'\')?\\b"); QRegularExpression findMajorAxis("\\b[Mm]ajor\\s*[Aa]xis:?\\s*(\\d{1,3}(?:\\.\\d{1,2})?)\\s*(°|\'|\"|\'\')?\\b"); QRegularExpression findMinorAxis("\\b[Mm]inor\\s*[Aa]xis:?\\s*(\\d{1,3}(?:\\.\\d{1,2})?)\\s*(°|\'|\"|\'\')?\\b"); QRegularExpression findPA( "\\b(?:[Pp]osition *[Aa]ngle|PA|[pP]\\.[aA]\\.):?\\s*(\\d{1,3}(\\.\\d{1,2})?)(?:°|[Ddeg])?\\b"); QRegularExpression findName1( "\\b(?:(?:[nN]ames?|NAMES?)[: ]|[iI]dent(?:ifier)?:|[dD]esignation:)\\h*\"?([-+\'A-Za-z0-9 ]*)\"?\\b"); QStringList catalogNames; catalogNames << "NGC" << "IC" << "M" << "PGC" << "UGC" << "UGCA" << "MCG" << "ESO" << "SDSS" << "LEDA" << "IRAS" << "PNG" << "Abell" << "ACO" << "HCG" << "CGCG" << "[IV]+Zw" << "Hickson" << "AGC" << "2MASS" << "RCS2" << "Terzan" << "PN [A-Z0-9]" << "VV" << "PK" << "GSC2" << "LBN" << "LDN" << "Caldwell" << "HIP" << "AM" << "vdB" << "Barnard" << "Shk"; QRegularExpression findName2("\\b(" + catalogNames.join("|") + ")\\s+(J?[-+0-9\\.]+[A-Da-h]?)\\b"); QRegularExpression findName3("\\b([A-Za-z]+[0-9]?)\\s+(J?[-+0-9]+[A-Da-h]?)\\b"); QString coordText; bool coordsFound = false, magFound = false, sizeFound = false, nameFound = false, positionAngleFound = false; dms RA, Dec; float mag = NaN::f; float majorAxis = NaN::f; float minorAxis = NaN::f; float positionAngle = 0; QString name; // Note: The following method is a proxy to support older versions of Qt. // In Qt 5.5 and above, the QString::indexOf(const QRegularExpression &re, int from, QRegularExpressionMatch *rmatch) method obviates the need for the following. auto indexOf = [](const QString &s, const QRegularExpression ®Exp, int from, QRegularExpressionMatch *m) -> int { *m = regExp.match(s, from); return m->capturedStart(0); }; auto countNonOverlappingMatches = [indexOf](const QString &string, const QRegularExpression ®Exp, - QStringList *list = 0) -> int { + QStringList *list = nullptr) -> int { int count = 0; int matchIndex = -1; int lastMatchLength = 1; QRegularExpressionMatch rmatch; while ((matchIndex = indexOf(string, regExp, matchIndex + lastMatchLength, &rmatch)) >= 0) { ++count; lastMatchLength = rmatch.captured(0).length(); if (list) list->append(rmatch.captured(0)); } return count; }; QRegularExpressionMatch rmatch; int nonOverlappingMatchCount; if ((nonOverlappingMatchCount = countNonOverlappingMatches(text, matchCoords)) == 2) { coordText = text; } else if (nonOverlappingMatchCount > 2) { qDebug() << "Found more than 2 coordinate matches. Trying to match J2000 line."; if (indexOf(text, matchJ2000Line, 0, &rmatch) >= 0) { coordText = rmatch.captured(1) + rmatch.captured(2); qDebug() << "Found a J2000 line match: " << coordText; } } if (!coordText.isEmpty()) { // int coord1 = indexOf(coordText, matchCoords, 0, &rmatch); // Q_ASSERT(coord1 >= 0); RA = dms(rmatch.captured(1) + ' ' + rmatch.captured(2) + ' ' + rmatch.captured(3), false); // int coord2 = indexOf(coordText, matchCoords, coord1 + rmatch.captured(0).length(), &rmatch); // Q_ASSERT(coord2 >= 0); Dec = dms(rmatch.captured(1) + ' ' + rmatch.captured(2) + ' ' + rmatch.captured(3), true); qDebug() << "Extracted coordinates: " << RA.toHMSString() << " " << Dec.toDMSString(); coordsFound = true; } else { if (text.contains(matchCoords2, &rmatch)) { QString matchString = rmatch.captured(0); QRegularExpression extractCoords2("(\\d\\d)(\\d\\d)(\\d\\d)([-+]\\d\\d)(\\d\\d)(\\d\\d)"); Q_ASSERT(matchString.contains(extractCoords2, &rmatch)); RA = dms(rmatch.captured(1) + ' ' + rmatch.captured(2) + ' ' + rmatch.captured(3), false); Dec = dms(rmatch.captured(4) + ' ' + rmatch.captured(5) + ' ' + rmatch.captured(6), true); coordsFound = true; } else { QStringList matches; qDebug() << "Could not extract RA/Dec. Found " << countNonOverlappingMatches(text, matchCoords, &matches) << " coordinate matches:"; qDebug() << matches; } } nameFound = true; if (text.contains(findName1, &rmatch)) // Explicit name search { qDebug() << "Found explicit name field: " << rmatch.captured(1) << " in text " << rmatch.captured(0); name = rmatch.captured(1); } else if (text.contains(findName2, &rmatch)) { qDebug() << "Found known catalog field: " << (name = rmatch.captured(1) + ' ' + rmatch.captured(2)) << " in text " << rmatch.captured(0); } else if (text.contains(findName3, &rmatch)) { qDebug() << "Found something that looks like a catalog designation: " << (name = rmatch.captured(1) + ' ' + rmatch.captured(2)) << " in text " << rmatch.captured(0); } else { qDebug() << "Could not find name."; nameFound = false; } magFound = true; if (text.contains(findMag1, &rmatch)) { qDebug() << "Found magnitude: " << rmatch.captured(1) << " in text " << rmatch.captured(0); mag = rmatch.captured(1).toFloat(); } else if (text.contains(findMag2, &rmatch)) { qDebug() << "Found magnitude: " << rmatch.captured(1) << " in text " << rmatch.captured(0); mag = rmatch.captured(1).toFloat(); } else { qDebug() << "Could not find magnitude."; magFound = false; } sizeFound = true; if (text.contains(findSize1, &rmatch)) { qDebug() << "Found size: " << rmatch.captured(1) << " x " << rmatch.captured(3) << " with units " << rmatch.captured(4) << " in text " << rmatch.captured(0); majorAxis = rmatch.captured(1).toFloat(); QString unitText2; if (rmatch.captured(2).isEmpty()) { unitText2 = rmatch.captured(4); } else { unitText2 = rmatch.captured(2); } if (unitText2.contains("°")) majorAxis *= 60; else if (unitText2.contains("\"") || unitText2.contains("\'\'")) majorAxis /= 60; minorAxis = rmatch.captured(3).toFloat(); if (rmatch.captured(4).contains("°")) minorAxis *= 60; else if (rmatch.captured(4).contains("\"") || rmatch.captured(4).contains("\'\'")) minorAxis /= 60; qDebug() << "Major axis = " << majorAxis << "; minor axis = " << minorAxis << " in arcmin"; } else if (text.contains(findSize2, &rmatch)) { majorAxis = rmatch.captured(1).toFloat(); if (rmatch.captured(2).contains("°")) majorAxis *= 60; else if (rmatch.captured(2).contains("\"") || rmatch.captured(2).contains("\'\'")) majorAxis /= 60; minorAxis = majorAxis; } else if (text.contains(findMajorAxis, &rmatch)) { majorAxis = rmatch.captured(1).toFloat(); if (rmatch.captured(2).contains("°")) majorAxis *= 60; else if (rmatch.captured(2).contains("\"") || rmatch.captured(2).contains("\'\'")) majorAxis /= 60; minorAxis = majorAxis; if (text.contains(findMinorAxis, &rmatch)) { minorAxis = rmatch.captured(1).toFloat(); if (rmatch.captured(2).contains("°")) minorAxis *= 60; else if (rmatch.captured(2).contains("\"") || rmatch.captured(2).contains("\'\'")) minorAxis /= 60; } } else { qDebug() << "Could not find size."; // FIXME: Improve to include separate major and minor axis matches, and size matches for round objects. sizeFound = false; } positionAngleFound = true; if (text.contains(findPA, &rmatch)) { qDebug() << "Found position angle: " << rmatch.captured(1) << " in text " << rmatch.captured(0); positionAngle = rmatch.captured(1).toFloat(); } else { qDebug() << "Could not find position angle."; positionAngleFound = false; } if (nameFound) ui->longNameEdit->setText(name); if (magFound) ui->visualMagnitudeInput->setValue(mag); // Improve band identification (V vs. B) if (coordsFound) { ui->raInput->setDMS(RA.toHMSString()); ui->decInput->setDMS(Dec.toDMSString()); } if (positionAngleFound) ui->positionAngleInput->setValue(positionAngle); if (sizeFound) { ui->majorAxisInput->setValue(majorAxis); ui->minorAxisInput->setValue(minorAxis); } } void AddDeepSkyObject::resetView() { ui->actualTypeDisplay->setText(SkyObject::typeName(SkyObject::TYPE_UNKNOWN)); ui->catalogIDInput->setValue(m_catalog->objectList().count()); ui->blueMagnitudeInput->setValue(99.99); ui->visualMagnitudeInput->setValue(99.99); ui->typeComboBox->setCurrentIndex(ui->typeComboBox->count() - 1); ui->majorAxisInput->setValue(0.0); ui->minorAxisInput->setValue(0.0); ui->positionAngleInput->setValue(0.0); ui->longNameEdit->setText(QString()); ui->raInput->setDMS(QString()); ui->decInput->setDMS(QString()); } bool AddDeepSkyObject::slotOk() { // Formulate a CatalogEntryData object CatalogEntryData centry; bool ok; centry.magnitude = ui->visualMagnitudeInput->value(); centry.flux = ui->blueMagnitudeInput->value(); centry.ra = ui->raInput->createDms(false, &ok).Degrees(); centry.dec = ui->decInput->createDms(true, &ok).Degrees(); centry.major_axis = ui->majorAxisInput->value(); centry.minor_axis = ui->minorAxisInput->value(); centry.long_name = ui->longNameEdit->text(); centry.type = ui->typeComboBox->currentIndex(); centry.position_angle = ui->positionAngleInput->value(); if (centry.type == ui->typeComboBox->count() - 1) centry.type = SkyObject::TYPE_UNKNOWN; // Insert it into the catalog bool success = m_catalog->addObject(centry); if (!success) { // Display error message - KMessageBox::sorry(0, i18n("Could not add deep-sky object. See console for error message!"), + KMessageBox::sorry(nullptr, i18n("Could not add deep-sky object. See console for error message!"), i18n("Add deep-sky object")); } // Accept the dialog return success; } void AddDeepSkyObject::slotFillFromText() { bool ok = false; QString text = QInputDialog::getMultiLineText(this, i18n("Add deep-sky object : enter text"), i18n("Enter the data to guess parameters from:"), QString(), &ok); if (ok) fillFromText(text); } diff --git a/kstars/tools/altvstime.cpp b/kstars/tools/altvstime.cpp index 56ea62bc4..bdd48a2a9 100644 --- a/kstars/tools/altvstime.cpp +++ b/kstars/tools/altvstime.cpp @@ -1,1520 +1,1520 @@ /*************************************************************************** altvstime.cpp - description ------------------- begin : wed nov 17 08:05:11 CET 2002 copyright : (C) 2002-2003 by Pablo de Vicente email : vicente@oan.es ***************************************************************************/ /*************************************************************************** * * * 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 "altvstime.h" #include "avtplotwidget.h" #include "dms.h" #include "ksalmanac.h" #include "kstarsdata.h" #include "kstarsdatetime.h" #include "ksnumbers.h" #include "simclock.h" #include "kssun.h" #include "dialogs/finddialog.h" #include "dialogs/locationdialog.h" #include "geolocation.h" #include "skyobjects/skypoint.h" #include "skyobjects/skyobject.h" #include "skyobjects/starobject.h" #include #include #include #include #include #include #include #include #include "kstars_debug.h" AltVsTimeUI::AltVsTimeUI(QWidget *p) : QFrame(p) { setupUi(this); } AltVsTime::AltVsTime(QWidget *parent) : QDialog(parent) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif setWindowTitle(i18n("Altitude vs. Time")); setModal(false); QVBoxLayout *topLayout = new QVBoxLayout; setLayout(topLayout); topLayout->setMargin(0); avtUI = new AltVsTimeUI(this); // Layers for setting up the plot's priority: the current curve should be above the other curves. // The Rise/Set/Transit markers should be on top, with highest priority. avtUI->View->addLayer("currentCurveLayer", avtUI->View->layer("main"), QCustomPlot::limAbove); avtUI->View->addLayer("markersLayer", avtUI->View->layer("currentCurveLayer"), QCustomPlot::limAbove); // Set up the Graph Window: avtUI->View->setBackground(QBrush(QColor(0, 0, 0))); avtUI->View->xAxis->grid()->setVisible(false); avtUI->View->yAxis->grid()->setVisible(false); QColor axisColor(Qt::white); QPen axisPen(axisColor, 1); avtUI->View->xAxis->setBasePen(axisPen); avtUI->View->xAxis->setTickPen(axisPen); avtUI->View->xAxis->setSubTickPen(axisPen); avtUI->View->xAxis->setTickLabelColor(axisColor); avtUI->View->xAxis->setLabelColor(axisColor); avtUI->View->xAxis2->setBasePen(axisPen); avtUI->View->xAxis2->setTickPen(axisPen); avtUI->View->xAxis2->setSubTickPen(axisPen); avtUI->View->xAxis2->setTickLabelColor(axisColor); avtUI->View->xAxis2->setLabelColor(axisColor); avtUI->View->yAxis->setBasePen(axisPen); avtUI->View->yAxis->setTickPen(axisPen); avtUI->View->yAxis->setSubTickPen(axisPen); avtUI->View->yAxis->setTickLabelColor(axisColor); avtUI->View->yAxis->setLabelColor(axisColor); avtUI->View->yAxis2->setBasePen(axisPen); avtUI->View->yAxis2->setTickPen(axisPen); avtUI->View->yAxis2->setSubTickPen(axisPen); avtUI->View->yAxis2->setTickLabelColor(axisColor); avtUI->View->yAxis2->setLabelColor(axisColor); // give the axis some labels: avtUI->View->xAxis2->setLabel("Local Sidereal Time"); avtUI->View->xAxis2->setVisible(true); avtUI->View->yAxis2->setVisible(true); avtUI->View->yAxis2->setTickLength(0, 0); avtUI->View->xAxis->setLabel("Local Time"); avtUI->View->yAxis->setLabel("Altitude"); avtUI->View->xAxis->setRange(43200, 129600); avtUI->View->xAxis2->setRange(61200, 147600); // configure the bottom axis to show time instead of number: QSharedPointer xAxisTimeTicker(new QCPAxisTickerTime); xAxisTimeTicker->setTimeFormat("%h:%m"); xAxisTimeTicker->setTickCount(12); xAxisTimeTicker->setTickStepStrategy(QCPAxisTicker::tssReadability); xAxisTimeTicker->setTickOrigin(Qt::UTC); avtUI->View->xAxis->setTicker(xAxisTimeTicker); // configure the top axis to show time instead of number: QSharedPointer xAxis2TimeTicker(new QCPAxisTickerTime); xAxis2TimeTicker->setTimeFormat("%h:%m"); xAxis2TimeTicker->setTickCount(12); xAxis2TimeTicker->setTickStepStrategy(QCPAxisTicker::tssReadability); xAxis2TimeTicker->setTickOrigin(Qt::UTC); avtUI->View->xAxis2->setTicker(xAxis2TimeTicker); // set up the Zoom/Pan features for the Top X Axis avtUI->View->axisRect()->setRangeDragAxes(avtUI->View->xAxis2, avtUI->View->yAxis); avtUI->View->axisRect()->setRangeZoomAxes(avtUI->View->xAxis2, avtUI->View->yAxis); // set up the margins, for a nice view avtUI->View->axisRect()->setAutoMargins(QCP::msBottom | QCP::msLeft | QCP::msTop); avtUI->View->axisRect()->setMargins(QMargins(0, 0, 7, 0)); // set up the interaction set: avtUI->View->setInteraction(QCP::iRangeZoom, true); avtUI->View->setInteraction(QCP::iRangeDrag, true); // set up the selection tolerance for checking if a certain graph is or not selected: avtUI->View->setSelectionTolerance(5); // draw the gradient: drawGradient(); // set up the background image: background = new QCPItemPixmap(avtUI->View); background->setPixmap(*gradient); background->topLeft->setType(QCPItemPosition::ptPlotCoords); background->bottomRight->setType(QCPItemPosition::ptPlotCoords); background->setScaled(true, Qt::IgnoreAspectRatio); background->setLayer("background"); background->setVisible(true); avtUI->raBox->setDegType(false); avtUI->decBox->setDegType(true); //FIXME: //Doesn't make sense to manually adjust long/lat unless we can modify TZ also avtUI->longBox->setReadOnly(true); avtUI->latBox->setReadOnly(true); topLayout->addWidget(avtUI); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); topLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); QPushButton *printB = new QPushButton( QIcon::fromTheme("document-print", QIcon(":/icons/breeze/default/document-print.svg")), i18n("&Print...")); printB->setToolTip(i18n("Print the Altitude vs. time plot")); buttonBox->addButton(printB, QDialogButtonBox::ActionRole); connect(printB, SIGNAL(clicked()), this, SLOT(slotPrint())); geo = KStarsData::Instance()->geo(); DayOffset = 0; // set up the initial minimum and maximum altitude minAlt = 0; maxAlt = 0; showCurrentDate(); if (getDate().time().hour() > 12) DayOffset = 1; avtUI->longBox->show(geo->lng()); avtUI->latBox->show(geo->lat()); computeSunRiseSetTimes(); setLSTLimits(); setDawnDusk(); connect(avtUI->View->yAxis, SIGNAL(rangeChanged(QCPRange)), this, SLOT(onYRangeChanged(QCPRange))); connect(avtUI->View->xAxis2, SIGNAL(rangeChanged(QCPRange)), this, SLOT(onXRangeChanged(QCPRange))); connect(avtUI->View, SIGNAL(plottableClick(QCPAbstractPlottable*,int,QMouseEvent*)), this, SLOT(plotMousePress(QCPAbstractPlottable*,int,QMouseEvent*))); connect(avtUI->View, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseOverLine(QMouseEvent*))); connect(avtUI->browseButton, SIGNAL(clicked()), this, SLOT(slotBrowseObject())); connect(avtUI->cityButton, SIGNAL(clicked()), this, SLOT(slotChooseCity())); connect(avtUI->updateButton, SIGNAL(clicked()), this, SLOT(slotUpdateDateLoc())); connect(avtUI->clearButton, SIGNAL(clicked()), this, SLOT(slotClear())); connect(avtUI->addButton, SIGNAL(clicked()), this, SLOT(slotAddSource())); connect(avtUI->nameBox, SIGNAL(returnPressed()), this, SLOT(slotAddSource())); connect(avtUI->raBox, SIGNAL(returnPressed()), this, SLOT(slotAddSource())); connect(avtUI->decBox, SIGNAL(returnPressed()), this, SLOT(slotAddSource())); connect(avtUI->clearFieldsButton, SIGNAL(clicked()), this, SLOT(slotClearBoxes())); connect(avtUI->longBox, SIGNAL(returnPressed()), this, SLOT(slotAdvanceFocus())); connect(avtUI->latBox, SIGNAL(returnPressed()), this, SLOT(slotAdvanceFocus())); connect(avtUI->PlotList, SIGNAL(currentRowChanged(int)), this, SLOT(slotHighlight(int))); connect(avtUI->computeButton, SIGNAL(clicked()), this, SLOT(slotComputeAltitudeByTime())); connect(avtUI->riseButton, SIGNAL(clicked()), this, SLOT(slotMarkRiseTime())); connect(avtUI->setButton, SIGNAL(clicked()), this, SLOT(slotMarkSetTime())); connect(avtUI->transitButton, SIGNAL(clicked()), this, SLOT(slotMarkTransitTime())); // Set up the Rise/Set/Transit buttons' icons: QPixmap redButton(100, 100); redButton.fill(Qt::transparent); QPainter p; p.begin(&redButton); p.setRenderHint(QPainter::Antialiasing, true); QPen pen(Qt::red, 2); p.setPen(pen); QBrush brush(Qt::red); p.setBrush(brush); p.drawEllipse(15, 15, 80, 80); p.end(); QPixmap blueButton(100, 100); blueButton.fill(Qt::transparent); QPainter p1; p1.begin(&blueButton); p1.setRenderHint(QPainter::Antialiasing, true); QPen pen1(Qt::blue, 2); p1.setPen(pen1); QBrush brush1(Qt::blue); p1.setBrush(brush1); p1.drawEllipse(15, 15, 80, 80); p1.end(); QPixmap greenButton(100, 100); greenButton.fill(Qt::transparent); QPainter p2; p2.begin(&greenButton); p2.setRenderHint(QPainter::Antialiasing, true); QPen pen2(Qt::green, 2); p2.setPen(pen2); QBrush brush2(Qt::green); p2.setBrush(brush2); p2.drawEllipse(15, 15, 80, 80); p2.end(); avtUI->riseButton->setIcon(QIcon(redButton)); avtUI->setButton->setIcon(QIcon(blueButton)); avtUI->transitButton->setIcon(QIcon(greenButton)); setMouseTracking(true); } AltVsTime::~AltVsTime() { //WARNING: need to delete deleteList items! } void AltVsTime::slotAddSource() { SkyObject *obj = KStarsData::Instance()->objectNamed(avtUI->nameBox->text()); if (obj) { //An object with the current name exists. If the object is not already //in the avt list, add it. bool found = false; foreach (SkyObject *o, pList) { //if ( o->name() == obj->name() ) { if (getObjectName(o, false) == getObjectName(obj, false)) { found = true; break; } } if (!found) processObject(obj); } else { //Object with the current name doesn't exist. It's possible that the //user is trying to add a custom object. Assume this is the case if //the RA and Dec fields are filled in. if (!avtUI->nameBox->text().isEmpty() && !avtUI->raBox->text().isEmpty() && !avtUI->decBox->text().isEmpty()) { bool okRA, okDec; dms newRA = avtUI->raBox->createDms(false, &okRA); dms newDec = avtUI->decBox->createDms(true, &okDec); if (!okRA || !okDec) return; //If the epochName is blank (or any non-double), we assume J2000 //Otherwise, precess to J2000. KStarsDateTime dt; dt.setFromEpoch(getEpoch(avtUI->epochName->text())); long double jd = dt.djd(); if (jd != J2000) { SkyPoint ptest(newRA, newDec); ptest.precessFromAnyEpoch(jd, J2000); newRA.setH(ptest.ra().Hours()); newDec.setD(ptest.dec().Degrees()); } //make sure the coords do not already exist from another object bool found = false; foreach (SkyObject *p, pList) { //within an arcsecond? if (fabs(newRA.Degrees() - p->ra().Degrees()) < 0.0003 && fabs(newDec.Degrees() - p->dec().Degrees()) < 0.0003) { found = true; break; } } if (!found) { SkyObject *obj = new SkyObject(8, newRA, newDec, 1.0, avtUI->nameBox->text()); deleteList.append(obj); //this object will be deleted when window is destroyed processObject(obj); } } //If the Ra and Dec boxes are filled, but the name field is empty, //move input focus to nameBox. If either coordinate box is empty, //move focus there if (avtUI->nameBox->text().isEmpty()) { avtUI->nameBox->QWidget::setFocus(); } if (avtUI->raBox->text().isEmpty()) { avtUI->raBox->QWidget::setFocus(); } else { if (avtUI->decBox->text().isEmpty()) avtUI->decBox->QWidget::setFocus(); } } avtUI->View->update(); } //Use find dialog to choose an object void AltVsTime::slotBrowseObject() { QPointer fd = new FindDialog(this); if (fd->exec() == QDialog::Accepted) { SkyObject *o = fd->targetObject(); processObject(o); } delete fd; avtUI->View->update(); avtUI->View->replot(); } void AltVsTime::processObject(SkyObject *o, bool forceAdd) { if (!o) return; KSNumbers *num = new KSNumbers(getDate().djd()); - KSNumbers *oldNum = 0; + KSNumbers *oldNum = nullptr; //If the object is in the solar system, recompute its position for the given epochLabel KStarsData *data = KStarsData::Instance(); if (o->isSolarSystem()) { oldNum = new KSNumbers(data->ut().djd()); o->updateCoords(num, true, geo->lat(), data->lst(), true); } //precess coords to target epoch o->updateCoordsNow(num); // vector used for computing the points needed for drawing the graph QVector y(100), t(100); //If this point is not in list already, add it to list bool found(false); foreach (SkyObject *p, pList) { if (o->ra().Degrees() == p->ra().Degrees() && o->dec().Degrees() == p->dec().Degrees()) { found = true; break; } } if (found && !forceAdd) { qCWarning(KSTARS) << "This point is already displayed; It will not be duplicated."; } else { pList.append(o); // make sure existing curves are thin and red: for (int i = 0; i < avtUI->View->graphCount(); i++) { if (avtUI->View->graph(i)->pen().color() == Qt::white) { avtUI->View->graph(i)->setPen(QPen(Qt::red, 2)); } } // SET up the curve's name avtUI->View->addGraph()->setName(o->name()); // compute the current graph: // time range: 24h int offset = 3; for (double h = -12.0, i = 0; h <= 12.0; h += 0.25, i++) { y[i] = findAltitude(o, h); if (y[i] > maxAlt) maxAlt = y[i]; if (y[i] < minAlt) minAlt = y[i]; t[i] = i * 900 + 43200; avtUI->View->graph(avtUI->View->graphCount() - 1)->addData(t[i], y[i]); } avtUI->View->graph(avtUI->View->graphCount() - 1)->setPen(QPen(Qt::white, 3)); // Go into initial state: without Zoom/Pan avtUI->View->xAxis->setRange(43200, 129600); avtUI->View->xAxis2->setRange(61200, 147600); if (abs(minAlt) > maxAlt) maxAlt = abs(minAlt); else minAlt = -maxAlt; avtUI->View->yAxis->setRange(minAlt - offset, maxAlt + offset); // Update background coordonates: background->topLeft->setCoords(avtUI->View->xAxis->range().lower, avtUI->View->yAxis->range().upper); background->bottomRight->setCoords(avtUI->View->xAxis->range().upper, avtUI->View->yAxis->range().lower); avtUI->View->replot(); avtUI->PlotList->addItem(getObjectName(o)); avtUI->PlotList->setCurrentRow(avtUI->PlotList->count() - 1); avtUI->raBox->showInHours(o->ra()); avtUI->decBox->showInDegrees(o->dec()); avtUI->nameBox->setText(getObjectName(o)); //Set epochName to epoch shown in date tab avtUI->epochName->setText(QString().setNum(getDate().epoch())); } //qCDebug() << "Currently, there are " << avtUI->View->graphCount() << " objects displayed."; //restore original position if (o->isSolarSystem()) { o->updateCoords(oldNum, true, data->geo()->lat(), data->lst(), true); delete oldNum; } o->EquatorialToHorizontal(data->lst(), data->geo()->lat()); delete num; } double AltVsTime::findAltitude(SkyPoint *p, double hour) { hour += 24.0 * DayOffset; //getDate converts the user-entered local time to UT KStarsDateTime ut = getDate().addSecs(hour * 3600.0); CachingDms LST = geo->GSTtoLST(ut.gst()); p->EquatorialToHorizontal(&LST, geo->lat()); return p->alt().Degrees(); } void AltVsTime::slotHighlight(int row) { if (row < 0) return; int rowIndex = 0; //highlight the curve of the selected object for (int i = 0; i < avtUI->View->graphCount(); i++) { if (i == row) rowIndex = row; else { avtUI->View->graph(i)->setPen(QPen(Qt::red, 2)); avtUI->View->graph(i)->setLayer("main"); } } avtUI->View->graph(rowIndex)->setPen(QPen(Qt::white, 3)); avtUI->View->graph(rowIndex)->setLayer("currentCurveLayer"); avtUI->View->update(); avtUI->View->replot(); if (row >= 0 && row < pList.size()) { SkyObject *p = pList.at(row); avtUI->raBox->showInHours(p->ra()); avtUI->decBox->showInDegrees(p->dec()); avtUI->nameBox->setText(avtUI->PlotList->currentItem()->text()); } SkyObject *selectedObject = KStarsData::Instance()->objectNamed(avtUI->nameBox->text()); const KStarsDateTime &ut = KStarsData::Instance()->ut(); if (selectedObject) { QTime rt = selectedObject->riseSetTime(ut, geo, true); //true = use rise time if (rt.isValid() == false) { avtUI->riseButton->setEnabled(false); avtUI->setButton->setEnabled(false); } else { avtUI->riseButton->setEnabled(true); avtUI->setButton->setEnabled(true); } } } void AltVsTime::onXRangeChanged(const QCPRange &range) { QCPRange aux = avtUI->View->xAxis2->range(); avtUI->View->xAxis->setRange(aux -= 18000); avtUI->View->xAxis2->setRange(range.bounded(61200, 147600)); // if ZOOM is detected then remove the gold lines that indicate current position: if (avtUI->View->xAxis->range().size() != 86400) { // Refresh the background: background->setScaled(false); background->setScaled(true, Qt::IgnoreAspectRatio); background->setPixmap(*gradient); avtUI->View->update(); avtUI->View->replot(); } } void AltVsTime::onYRangeChanged(const QCPRange &range) { int offset = 3; avtUI->View->yAxis->setRange(range.bounded(minAlt - offset, maxAlt + offset)); } void AltVsTime::plotMousePress(QCPAbstractPlottable *abstractPlottable, int dataIndex, QMouseEvent *event) { //Do we need this? Q_UNUSED(dataIndex); if (event->button() == Qt::RightButton) { QCPAbstractPlottable *plottable = abstractPlottable; if (plottable) { double x = avtUI->View->xAxis->pixelToCoord(event->localPos().x()); double y = avtUI->View->yAxis->pixelToCoord(event->localPos().y()); QCPGraph *graph = qobject_cast(plottable); if (graph) { double yValue = y; double xValue = x; // Compute time value: QTime localTime(0, 0, 0, 0); QTime localSiderealTime(5, 0, 0, 0); localTime = localTime.addSecs(int(xValue)); localSiderealTime = localSiderealTime.addSecs(int(xValue)); QToolTip::hideText(); QToolTip::showText(event->globalPos(), tr("" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "
%L1
LST: %L3
LT: %L2
Altitude: %L4
") .arg((graph->name().isEmpty() ? "???" : graph->name()), localTime.toString(), localSiderealTime.toString(), QString::number(yValue, 'f', 2) + ' ' + QChar(176)), avtUI->View, avtUI->View->rect()); } } } } //move input focus to the next logical widget void AltVsTime::slotAdvanceFocus() { if (sender()->objectName() == QString("nameBox")) avtUI->addButton->setFocus(); if (sender()->objectName() == QString("raBox")) avtUI->decBox->setFocus(); if (sender()->objectName() == QString("decbox")) avtUI->addButton->setFocus(); if (sender()->objectName() == QString("longBox")) avtUI->latBox->setFocus(); if (sender()->objectName() == QString("latBox")) avtUI->updateButton->setFocus(); } void AltVsTime::slotClear() { pList.clear(); //Need to delete the pointers in deleteList while (!deleteList.isEmpty()) delete deleteList.takeFirst(); avtUI->PlotList->clear(); avtUI->nameBox->clear(); avtUI->raBox->clear(); avtUI->decBox->clear(); avtUI->epochName->clear(); // remove all graphs from the plot: avtUI->View->clearGraphs(); // we remove all the dots (rise/set/transit) from the chart // without removing the background image int indexItem = 0, noItems = avtUI->View->itemCount(); QCPAbstractItem *item; QCPItemPixmap *background; // remove every item at a time: while (noItems > 1 && indexItem < noItems) { // test if the current item is the background: item = avtUI->View->item(indexItem); background = qobject_cast(item); if (background) indexItem++; else { // if not, then remove this item: avtUI->View->removeItem(indexItem); noItems--; } } // update & replot the chart: avtUI->View->update(); avtUI->View->replot(); } void AltVsTime::slotClearBoxes() { avtUI->nameBox->clear(); avtUI->raBox->clear(); avtUI->decBox->clear(); avtUI->epochName->clear(); } void AltVsTime::slotComputeAltitudeByTime() { // FIXME Migrate to QCustomPlot 2.0 #if 0 // check if at least one graph exists in the plot if( avtUI->View->graphCount() > 0 ) { // get the time from the time spin box QTime timeFormat = avtUI->timeSpin->time(); double hours = timeFormat.hour(); double minutes = timeFormat.minute(); // convert the hours over 24 to correct their values if( hours < 12 ) hours += 24; double timeValue = hours * 3600 + minutes * 60; QCPGraph * selectedGraph; // get the graph's name from the name box QString graphName = avtUI->nameBox->text(); // find the graph index int graphIndex = 0; for( int i=0; iView->graphCount(); i++ ) if( avtUI->View->graph(i)->name().compare(graphName) == 0 ) { graphIndex = i; break; } selectedGraph = avtUI->View->graph(graphIndex); // get the data from the selected graph QCPDataMap * dataMap = selectedGraph->data(); double averageAltitude = 0; double altitude1 = dataMap->lowerBound(timeValue-899).value().value; double altitude2 = dataMap->lowerBound(timeValue).value().value; double time1 = dataMap->lowerBound(timeValue-899).value().key; averageAltitude = (altitude1+altitude2)/2; // short algorithm to compute the right altitude for a certain time if( timeValue > time1 ) { if( timeValue - time1 < 225 ) averageAltitude = altitude1; else if( timeValue - time1 < 675 ) averageAltitude = (altitude1+altitude2)/2; else averageAltitude = altitude2; } // set the altitude in the altitude box avtUI->altitudeBox->setText( QString::number(averageAltitude, 'f', 2) ); } #endif } void AltVsTime::slotMarkRiseTime() { const KStarsDateTime &ut = KStarsData::Instance()->ut(); SkyObject *selectedObject = pList.at(avtUI->PlotList->currentRow()); if (selectedObject == nullptr) { qCWarning(KSTARS) << "Mark Rise Time: Unable to find" << avtUI->PlotList->currentItem()->text(); return; } QCPItemTracer *riseTimeTracer; // check if at least one graph exists in the plot if (avtUI->View->graphCount() > 0) { double time = 0; double hours, minutes; QCPGraph *selectedGraph = avtUI->View->graph(avtUI->PlotList->currentRow()); QTime rt = selectedObject->riseSetTime(ut, geo, true); //true = use rise time // mark the Rise time with a solid red circle if (rt.isValid() && selectedGraph) { hours = rt.hour(); minutes = rt.minute(); if (hours < 12) hours += 24; time = hours * 3600 + minutes * 60; riseTimeTracer = new QCPItemTracer(avtUI->View); riseTimeTracer->setLayer("markersLayer"); riseTimeTracer->setGraph(selectedGraph); riseTimeTracer->setInterpolating(true); riseTimeTracer->setStyle(QCPItemTracer::tsCircle); riseTimeTracer->setPen(QPen(Qt::red)); riseTimeTracer->setBrush(Qt::red); riseTimeTracer->setSize(10); riseTimeTracer->setGraphKey(time); riseTimeTracer->setVisible(true); avtUI->View->update(); avtUI->View->replot(); } } } void AltVsTime::slotMarkSetTime() { const KStarsDateTime &ut = KStarsData::Instance()->ut(); SkyObject *selectedObject = pList.at(avtUI->PlotList->currentRow()); if (selectedObject == nullptr) { qCWarning(KSTARS) << "Mark Set Time: Unable to find" << avtUI->PlotList->currentItem()->text(); return; } QCPItemTracer *setTimeTracer; // check if at least one graph exists in the plot if (avtUI->View->graphCount() > 0) { double time = 0; double hours, minutes; QCPGraph *selectedGraph = avtUI->View->graph(avtUI->PlotList->currentRow()); QTime rt = selectedObject->riseSetTime(ut, geo, true); //true = use rise time //If set time is before rise time, use set time for tomorrow QTime st = selectedObject->riseSetTime(ut, geo, false); //false = use set time if (st < rt) st = selectedObject->riseSetTime(ut.addDays(1), geo, false); //false = use set time // mark the Set time with a solid blue circle if (rt.isValid()) { hours = st.hour(); minutes = st.minute(); if (hours < 12) hours += 24; time = hours * 3600 + minutes * 60; setTimeTracer = new QCPItemTracer(avtUI->View); setTimeTracer->setLayer("markersLayer"); setTimeTracer->setGraph(selectedGraph); setTimeTracer->setInterpolating(true); setTimeTracer->setStyle(QCPItemTracer::tsCircle); setTimeTracer->setPen(QPen(Qt::blue)); setTimeTracer->setBrush(Qt::blue); setTimeTracer->setSize(10); setTimeTracer->setGraphKey(time); setTimeTracer->setVisible(true); avtUI->View->update(); avtUI->View->replot(); } } } void AltVsTime::slotMarkTransitTime() { const KStarsDateTime &ut = KStarsData::Instance()->ut(); SkyObject *selectedObject = pList.at(avtUI->PlotList->currentRow()); if (selectedObject == nullptr) { qCWarning(KSTARS) << "Mark Transit Time: Unable to find" << avtUI->PlotList->currentItem()->text(); return; } QCPItemTracer *transitTimeTracer; // check if at least one graph exists in the plot if (avtUI->View->graphCount() > 0) { double time = 0; double hours, minutes; QCPGraph *selectedGraph = avtUI->View->graph(avtUI->PlotList->currentRow()); QTime rt = selectedObject->riseSetTime(ut, geo, true); //true = use rise time //If transit time is before rise time, use transit time for tomorrow QTime tt = selectedObject->transitTime(ut, geo); if (tt < rt) tt = selectedObject->transitTime(ut.addDays(1), geo); // mark the Transit time with a solid green circle hours = tt.hour(); minutes = tt.minute(); if (hours < 12) hours += 24; time = hours * 3600 + minutes * 60; transitTimeTracer = new QCPItemTracer(avtUI->View); transitTimeTracer->setLayer("markersLayer"); transitTimeTracer->setGraph(selectedGraph); transitTimeTracer->setInterpolating(true); transitTimeTracer->setStyle(QCPItemTracer::tsCircle); transitTimeTracer->setPen(QPen(Qt::green)); transitTimeTracer->setBrush(Qt::green); transitTimeTracer->setSize(10); transitTimeTracer->setGraphKey(time); transitTimeTracer->setVisible(true); avtUI->View->update(); avtUI->View->replot(); } } void AltVsTime::computeSunRiseSetTimes() { //Determine the time of sunset and sunrise for the desired date and location //expressed as doubles, the fraction of a full day. KStarsDateTime today = getDate(); KSAlmanac ksal; ksal.setDate(&today); ksal.setLocation(geo); } //FIXME /* void AltVsTime::mouseOverLine(QMouseEvent *event){ // Get the mouse position's coordinates relative to axes: double x = avtUI->View->xAxis->pixelToCoord(event->pos().x()); double y = avtUI->View->yAxis->pixelToCoord(event->pos().y()); // Save the actual values: double yValue = y; double xValue = x; // The offset used for the Y axis: top/bottom int offset = 3; // Compute the Y axis maximum value: int yAxisMaxValue = maxAlt + offset; // Compute the X axis minimum and maximum values: int xAxisMinValue = 43200; int xAxisMaxValue = 129600; // Ignore the upper and left margins: y = yAxisMaxValue - y; x -= xAxisMinValue; // We make a copy to gradient background in order to have one set of lines at a time: // Otherwise, the chart would have been covered by lines QPixmap copy = gradient->copy(gradient->rect()); // If ZOOM is not active, then draw the gold lines that indicate current mouse pisition: if(avtUI->View->xAxis->range().size() == 86400){ QPainter p; p.begin(©); p.setPen( QPen( QBrush("gold"), 2, Qt::SolidLine ) ); // Get the gradient background's width and height: int pW = gradient->rect().width(); int pH = gradient->rect().height(); // Compute the real coordinates within the chart: y = (y*pH/2)/yAxisMaxValue; x = (x*pW)/(xAxisMaxValue-xAxisMinValue); // Draw the horizontal line (altitude): p.drawLine( QLineF( 0.5, y, avtUI->View->rect().width()-0.5,y ) ); // Draw the altitude value: p.setPen( QPen( QBrush("gold"), 3, Qt::SolidLine ) ); p.drawText( 25, y + 15, QString::number(yValue,'f',2) + QChar(176) ); p.setPen( QPen( QBrush("gold"), 1, Qt::SolidLine ) ); // Draw the vertical line (time): p.drawLine( QLineF( x, 0.5, x, avtUI->View->rect().height()-0.5 ) ); // Compute and draw the time value: QTime localTime(0,0,0,0); localTime = localTime.addSecs(int(xValue)); p.save(); p.translate( x + 10, pH - 20 ); p.rotate(-90); p.setPen( QPen( QBrush("gold"), 3, Qt::SolidLine ) ); p.drawText( 5, 5, QLocale().toString( localTime, QLocale::ShortFormat ) ); // short format necessary to avoid false time-zone labeling p.restore(); p.end(); } // Refresh the background: background->setScaled(false); background->setScaled(true, Qt::IgnoreAspectRatio); background->setPixmap(copy); avtUI->View->update(); avtUI->View->replot(); } */ void AltVsTime::mouseOverLine(QMouseEvent *event) { double x = avtUI->View->xAxis->pixelToCoord(event->localPos().x()); double y = avtUI->View->yAxis->pixelToCoord(event->localPos().y()); QCPAbstractPlottable *abstractGraph = avtUI->View->plottableAt(event->pos(), false); QCPGraph *graph = qobject_cast(abstractGraph); if (x > avtUI->View->xAxis->range().lower && x < avtUI->View->xAxis->range().upper) if (y > avtUI->View->yAxis->range().lower && y < avtUI->View->yAxis->range().upper) { if (graph) { double yValue = y; double xValue = x; // Compute time value: QTime localTime(0, 0, 0, 0); QTime localSiderealTime(5, 0, 0, 0); localTime = localTime.addSecs(int(xValue)); localSiderealTime = localSiderealTime.addSecs(int(xValue)); QToolTip::hideText(); QToolTip::showText(event->globalPos(), tr("" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "
%L1
LST: %L3
LT: %L2
Altitude: %L4
") .arg((graph->name().isEmpty() ? "???" : graph->name()), localTime.toString(), localSiderealTime.toString(), QString::number(yValue, 'f', 2) + ' ' + QChar(176)), avtUI->View, avtUI->View->rect()); } else QToolTip::hideText(); } avtUI->View->update(); avtUI->View->replot(); } void AltVsTime::slotUpdateDateLoc() { KStarsData *data = KStarsData::Instance(); KStarsDateTime today = getDate(); KSNumbers *num = new KSNumbers(today.djd()); - KSNumbers *oldNum = 0; + KSNumbers *oldNum = nullptr; CachingDms LST = geo->GSTtoLST(today.gst()); //First determine time of sunset and sunrise computeSunRiseSetTimes(); // Determine dawn/dusk time and min/max sun elevation setDawnDusk(); for (int i = 0; i < pList.count(); ++i) { SkyObject *o = pList.at(i); if (o) { //If the object is in the solar system, recompute its position for the given date if (o->isSolarSystem()) { oldNum = new KSNumbers(data->ut().djd()); o->updateCoords(num, true, geo->lat(), &LST, true); } //precess coords to target epoch o->updateCoordsNow(num); //update pList entry pList.replace(i, o); // We are creating a new data set (time, altitude) for the new date: QVector time_dataSet, altitude_dataSet; double point_altitudeValue, point_timeValue; // compute the new graph values: // time range: 24h int offset = 3; for (double h = -12.0, i = 0; h <= 12.0; h += 0.25, i++) { point_altitudeValue = findAltitude(o, h); altitude_dataSet.push_back(point_altitudeValue); if (point_altitudeValue > maxAlt) maxAlt = point_altitudeValue; if (point_altitudeValue < minAlt) minAlt = point_altitudeValue; point_timeValue = i * 900 + 43200; time_dataSet.push_back(point_timeValue); } // Replace graph data set: avtUI->View->graph(i)->setData(time_dataSet, altitude_dataSet); // Go into initial state: without Zoom/Pan avtUI->View->xAxis->setRange(43200, 129600); avtUI->View->xAxis2->setRange(61200, 147600); // Center the altitude axis in 0 value: if (abs(minAlt) > maxAlt) maxAlt = abs(minAlt); else minAlt = -maxAlt; avtUI->View->yAxis->setRange(minAlt - offset, maxAlt + offset); // Update background coordonates: background->topLeft->setCoords(avtUI->View->xAxis->range().lower, avtUI->View->yAxis->range().upper); background->bottomRight->setCoords(avtUI->View->xAxis->range().upper, avtUI->View->yAxis->range().lower); // Redraw the plot: avtUI->View->replot(); //restore original position if (o->isSolarSystem()) { o->updateCoords(oldNum, true, data->geo()->lat(), data->lst()); delete oldNum; - oldNum = 0; + oldNum = nullptr; } o->EquatorialToHorizontal(data->lst(), data->geo()->lat()); } else //assume unfound object is a custom object { pList.at(i)->updateCoordsNow(num); //precess to desired epoch // We are creating a new data set (time, altitude) for the new date: QVector time_dataSet, altitude_dataSet; double point_altitudeValue, point_timeValue; // compute the new graph values: // time range: 24h int offset = 3; for (double h = -12.0, i = 0; h <= 12.0; h += 0.25, i++) { point_altitudeValue = findAltitude(pList.at(i), h); altitude_dataSet.push_back(point_altitudeValue); if (point_altitudeValue > maxAlt) maxAlt = point_altitudeValue; if (point_altitudeValue < minAlt) minAlt = point_altitudeValue; point_timeValue = i * 900 + 43200; time_dataSet.push_back(point_timeValue); } // Replace graph data set: avtUI->View->graph(i)->setData(time_dataSet, altitude_dataSet); // Go into initial state: without Zoom/Pan avtUI->View->xAxis->setRange(43200, 129600); avtUI->View->xAxis2->setRange(61200, 147600); // Center the altitude axis in 0 value: if (abs(minAlt) > maxAlt) maxAlt = abs(minAlt); else minAlt = -maxAlt; avtUI->View->yAxis->setRange(minAlt - offset, maxAlt + offset); // Update background coordonates: background->topLeft->setCoords(avtUI->View->xAxis->range().lower, avtUI->View->yAxis->range().upper); background->bottomRight->setCoords(avtUI->View->xAxis->range().upper, avtUI->View->yAxis->range().lower); // Redraw the plot: avtUI->View->replot(); } } if (getDate().time().hour() > 12) DayOffset = 1; else DayOffset = 0; setLSTLimits(); slotHighlight(avtUI->PlotList->currentRow()); avtUI->View->update(); delete num; } void AltVsTime::slotChooseCity() { QPointer ld = new LocationDialog(this); if (ld->exec() == QDialog::Accepted) { GeoLocation *newGeo = ld->selectedCity(); if (newGeo) { geo = newGeo; avtUI->latBox->showInDegrees(geo->lat()); avtUI->longBox->showInDegrees(geo->lng()); } } delete ld; } // FIXME: should we remove this method? void AltVsTime::setLSTLimits() { /* //UT at noon on target date KStarsDateTime ut = getDate().addSecs(((double)DayOffset + 0.5)*86400.); dms lst = geo->GSTtoLST(ut.gst()); double h1 = lst.Hours(); if(h1 > 12.0) h1 -= 24.0; double h2 = h1 + 24.0; avtUI->View->setSecondaryLimits(h1, h2, -90.0, 90.0); */ } void AltVsTime::showCurrentDate() { KStarsDateTime dt = KStarsDateTime::currentDateTime(); if (dt.time() > QTime(12, 0, 0)) dt = dt.addDays(1); avtUI->DateWidget->setDate(dt.date()); } void AltVsTime::drawGradient() { // Things needed for Gradient: KSAlmanac ksal; KStarsDateTime dtt = KStarsDateTime::currentDateTime(); GeoLocation *geoLoc = KStarsData::Instance()->geo(); QDateTime midnight = QDateTime(dtt.date(), QTime()); KStarsDateTime utt = geoLoc->LTtoUT(KStarsDateTime(midnight)); // Variables needed for Gradient: double SunRise, SunSet, Dawn, Dusk, SunMinAlt, SunMaxAlt; double MoonRise, MoonSet, MoonIllum; ksal.setLocation(geoLoc); ksal.setDate(&utt); // Get the values: SunRise = ksal.getSunRise(); SunSet = ksal.getSunSet(); SunMaxAlt = ksal.getSunMaxAlt(); SunMinAlt = ksal.getSunMinAlt(); MoonRise = ksal.getMoonRise(); MoonSet = ksal.getMoonSet(); MoonIllum = ksal.getMoonIllum(); Dawn = ksal.getDawnAstronomicalTwilight(); Dusk = ksal.getDuskAstronomicalTwilight(); gradient = new QPixmap(avtUI->View->rect().width(), avtUI->View->rect().height()); QPainter p; p.begin(gradient); KPlotWidget *kPW = new KPlotWidget; p.setRenderHint(QPainter::Antialiasing, kPW->antialiasing()); p.fillRect(gradient->rect(), kPW->backgroundColor()); p.setClipRect(gradient->rect()); p.setClipping(true); int pW = gradient->rect().width(); int pH = gradient->rect().height(); QColor SkyColor(0, 100, 200); // TODO // if( Options::darkAppColors() ) // SkyColor = QColor( 200, 0, 0 ); // use something red, visible through a red filter // Draw gradient representing lunar interference in the sky if (MoonIllum > 0.01) // do this only if Moon illumination is reasonable so it's important { int moonrise = int(pW * (0.5 + MoonRise)); int moonset = int(pW * (MoonSet - 0.5)); if (moonset < 0) moonset += pW; if (moonrise > pW) moonrise -= pW; int moonalpha = int(10 + MoonIllum * 130); int fadewidth = pW * 0.01; // pW * fraction of day to fade the moon brightness over (0.01 corresponds to roughly 15 minutes, 0.007 to 10 minutes), both before and after actual set. QColor MoonColor(255, 255, 255, moonalpha); if (moonset < moonrise) { QLinearGradient grad = QLinearGradient(QPointF(moonset - fadewidth, 0.0), QPointF(moonset + fadewidth, 0.0)); grad.setColorAt(0, MoonColor); grad.setColorAt(1, Qt::transparent); p.fillRect(QRectF(0.0, 0.0, moonset + fadewidth, pH), grad); // gradient should be padded until moonset - fadewidth (see QLinearGradient docs) grad.setStart(QPointF(moonrise + fadewidth, 0.0)); grad.setFinalStop(QPointF(moonrise - fadewidth, 0.0)); p.fillRect(QRectF(moonrise - fadewidth, 0.0, pW - moonrise + fadewidth, pH), grad); } else { qreal opacity = p.opacity(); p.setOpacity(opacity / 4); p.fillRect(QRectF(moonrise + fadewidth, 0.0, moonset - moonrise - 2 * fadewidth, pH), MoonColor); QLinearGradient grad = QLinearGradient(QPointF(moonrise + fadewidth, 0.0), QPointF(moonrise - fadewidth, 0.0)); grad.setColorAt(0, MoonColor); grad.setColorAt(1, Qt::transparent); p.fillRect(QRectF(0.0, 0.0, moonrise + fadewidth, pH), grad); grad.setStart(QPointF(moonset - fadewidth, 0.0)); grad.setFinalStop(QPointF(moonset + fadewidth, 0.0)); p.fillRect(QRectF(moonset - fadewidth, 0.0, pW - moonset, pH), grad); p.setOpacity(opacity); } } //draw daytime sky if the Sun rises for the current date/location if (SunMaxAlt > -18.0) { //Display centered on midnight, so need to modulate dawn/dusk by 0.5 int rise = int(pW * (0.5 + SunRise)); int set = int(pW * (SunSet - 0.5)); int da = int(pW * (0.5 + Dawn)); int du = int(pW * (Dusk - 0.5)); if (SunMinAlt > 0.0) { // The sun never set and the sky is always blue p.fillRect(rect(), SkyColor); } else if (SunMaxAlt < 0.0 && SunMinAlt < -18.0) { // The sun never rise but the sky is not completely dark QLinearGradient grad = QLinearGradient(QPointF(0.0, 0.0), QPointF(du, 0.0)); QColor gradStartColor = SkyColor; gradStartColor.setAlpha((1 - (SunMaxAlt / -18.0)) * 255); grad.setColorAt(0, gradStartColor); grad.setColorAt(1, Qt::transparent); p.fillRect(QRectF(0.0, 0.0, du, pH), grad); grad.setStart(QPointF(pW, 0.0)); grad.setFinalStop(QPointF(da, 0.0)); p.fillRect(QRectF(da, 0.0, pW, pH), grad); } else if (SunMaxAlt < 0.0 && SunMinAlt > -18.0) { // The sun never rise but the sky is NEVER completely dark QLinearGradient grad = QLinearGradient(QPointF(0.0, 0.0), QPointF(pW, 0.0)); QColor gradStartEndColor = SkyColor; gradStartEndColor.setAlpha((1 - (SunMaxAlt / -18.0)) * 255); QColor gradMidColor = SkyColor; gradMidColor.setAlpha((1 - (SunMinAlt / -18.0)) * 255); grad.setColorAt(0, gradStartEndColor); grad.setColorAt(0.5, gradMidColor); grad.setColorAt(1, gradStartEndColor); p.fillRect(QRectF(0.0, 0.0, pW, pH), grad); } else if (Dawn < 0.0) { // The sun sets and rises but the sky is never completely dark p.fillRect(0, 0, set, int(0.5 * pH), SkyColor); p.fillRect(rise, 0, pW, int(0.5 * pH), SkyColor); QLinearGradient grad = QLinearGradient(QPointF(set, 0.0), QPointF(rise, 0.0)); QColor gradMidColor = SkyColor; gradMidColor.setAlpha((1 - (SunMinAlt / -18.0)) * 255); grad.setColorAt(0, SkyColor); grad.setColorAt(0.5, gradMidColor); grad.setColorAt(1, SkyColor); p.fillRect(QRectF(set, 0.0, rise - set, pH), grad); } else { p.fillRect(0, 0, set, pH, SkyColor); p.fillRect(rise, 0, pW, pH, SkyColor); QLinearGradient grad = QLinearGradient(QPointF(set, 0.0), QPointF(du, 0.0)); grad.setColorAt(0, SkyColor); grad.setColorAt( 1, Qt::transparent); // FIXME?: The sky appears black well before the actual end of twilight if the gradient is too slow (eg: latitudes above arctic circle) p.fillRect(QRectF(set, 0.0, du - set, pH), grad); grad.setStart(QPointF(rise, 0.0)); grad.setFinalStop(QPointF(da, 0.0)); p.fillRect(QRectF(da, 0.0, rise - da, pH), grad); } } p.fillRect(0, int(0.5 * pH), pW, int(0.5 * pH), KStarsData::Instance()->colorScheme()->colorNamed("HorzColor")); p.setClipping(false); // Add vertical line indicating "now" // Convert the current system clock time to the TZ corresponding to geo QTime t = geoLoc->UTtoLT(KStarsDateTime::currentDateTimeUtc()).time(); double x = 12.0 + t.hour() + t.minute() / 60.0 + t.second() / 3600.0; while (x > 24.0) x -= 24.0; // Convert to screen pixel coords int ix = int(x * pW / 24.0); p.setPen(QPen(QBrush("white"), 2.0, Qt::DotLine)); p.drawLine(ix, 0, ix, pH); QFont largeFont = p.font(); largeFont.setPointSize(largeFont.pointSize() + 1); // Label this vertical line with the current time p.save(); p.setFont(largeFont); p.translate(ix + 10, pH - 20); p.rotate(-90); // Short format necessary to avoid false time-zone labeling p.drawText(0, 0, QLocale().toString(t, QLocale::ShortFormat)); p.restore(); p.end(); } KStarsDateTime AltVsTime::getDate() { //convert midnight local time to UT: QDateTime lt(avtUI->DateWidget->date(), QTime()); return geo->LTtoUT(KStarsDateTime(lt)); } double AltVsTime::getEpoch(const QString &eName) { //If Epoch field not a double, assume J2000 bool ok; double epoch = eName.toDouble(&ok); if (!ok) { qCWarning(KSTARS) << "Invalid Epoch. Assuming 2000.0."; return 2000.0; } return epoch; } void AltVsTime::setDawnDusk() { KStarsDateTime today = getDate(); KSNumbers num(today.djd()); CachingDms LST = geo->GSTtoLST(today.gst()); KSSun sun; sun.updateCoords(&num, true, geo->lat(), &LST, true); /* TODO: double last_h = -12.0; double last_alt = findAltitude( &sun, last_h ); double dawn = -13.0; double dusk = -13.0; double max_alt = -100.0; double min_alt = 100.0; for ( double h=-11.95; h<=12.0; h+=0.05 ) { double alt = findAltitude( &sun, h ); bool asc = alt - last_alt > 0; if ( alt > max_alt ) max_alt = alt; if ( alt < min_alt ) min_alt = alt; if ( asc && last_alt <= -18.0 && alt >= -18.0 ) dawn = h; if ( !asc && last_alt >= -18.0 && alt <= -18.0 ) dusk = h; last_h = h; last_alt = alt; } if ( dawn < -12.0 || dusk < -12.0 ) { da = -1.0; du = -1.0; } else { da = dawn / 24.0; du = ( dusk + 24.0 ) / 24.0; } avtUI->View->setDawnDuskTimes( da, du ); avtUI->View->setMinMaxSunAlt( min_alt, max_alt ); */ } void AltVsTime::slotPrint() { QPainter p; // Our painter object QPrinter printer; // Our printer object QString str_legend; // Text legend int text_height = 200; // Height of legend text zone in points QSize plot_size; // Initial plot widget size QFont plot_font; // Initial plot widget font int plot_font_size; // Initial plot widget font size // Set printer resolution to 300 dpi printer.setResolution(300); // Open print dialog //QPointer dialog( KdePrint::createPrintDialog( &printer, this ) ); //QPointer dialog( &printer, this ); QPrintDialog dialog(&printer, this); dialog.setWindowTitle(i18n("Print elevation vs time plot")); if (dialog.exec() == QDialog::Accepted) { // Change mouse cursor QApplication::setOverrideCursor(Qt::WaitCursor); // Save plot widget font plot_font = avtUI->View->font(); // Save plot widget font size plot_font_size = plot_font.pointSize(); // Save calendar widget size plot_size = avtUI->View->size(); // Set text legend str_legend = i18n("Elevation vs. Time Plot"); str_legend += '\n'; str_legend += geo->fullName(); str_legend += " - "; str_legend += avtUI->DateWidget->date().toString("dd/MM/yyyy"); // Create a rectangle for legend text zone QRect text_rect(0, 0, printer.width(), text_height); // Increase plot widget font size so it looks good in 300 dpi plot_font.setPointSize(plot_font_size * 2.5); avtUI->View->setFont(plot_font); // Increase plot widget size to fit the entire page avtUI->View->resize(printer.width(), printer.height() - text_height); // Create a pixmap and render plot widget into it QPixmap pixmap(avtUI->View->size()); avtUI->View->render(&pixmap); // Begin painting on printer p.begin(&printer); // Draw legend p.drawText(text_rect, Qt::AlignLeft, str_legend); // Draw plot widget p.drawPixmap(0, text_height, pixmap); // Ending painting p.end(); // Restore plot widget font size plot_font.setPointSize(plot_font_size); avtUI->View->setFont(plot_font); // Restore calendar widget size avtUI->View->resize(plot_size); // Restore mouse cursor QApplication::restoreOverrideCursor(); } //delete dialog; } QString AltVsTime::getObjectName(const SkyObject *o, bool translated) { QString finalObjectName; if (o->name() == "star") { StarObject *s = (StarObject *)o; // JM: Enable HD Index stars to be added to the observing list. if (s->getHDIndex() != 0) finalObjectName = QString("HD %1").arg(QString::number(s->getHDIndex())); } else finalObjectName = translated ? o->translatedName() : o->name(); return finalObjectName; } diff --git a/kstars/tools/altvstime.h b/kstars/tools/altvstime.h index 4c84d5f4b..41ce65829 100644 --- a/kstars/tools/altvstime.h +++ b/kstars/tools/altvstime.h @@ -1,209 +1,209 @@ /*************************************************************************** altvstime.h - description ------------------- begin : Mon Dec 23 2002 copyright : (C) 2002 by Pablo de Vicente email : vicente@oan.es ***************************************************************************/ /*************************************************************************** * * * 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 #include #include "ui_altvstime.h" class QCPAbstractPlottable; class QCPItemPixmap; class QCPRange; class QMouseEvent; class QPixmap; class GeoLocation; class KStarsDateTime; class SkyObject; class SkyPoint; class AltVsTimeUI : public QFrame, public Ui::AltVsTime { Q_OBJECT public: - explicit AltVsTimeUI(QWidget *p = 0); + explicit AltVsTimeUI(QWidget *p = nullptr); }; /** * @class AltVsTime * @short the Altitude vs. Time Tool. * Plot the altitude as a function of time for any list of * objects, as seen from any location, on any date. * * @author Jason Harris */ class AltVsTime : public QDialog { Q_OBJECT public: /** Constructor */ explicit AltVsTime(QWidget *parent = nullptr); /** Destructor */ ~AltVsTime() override; /** * Determine the limits for the sideral time axis, using * the sidereal time at midnight for the current date * and location settings. */ void setLSTLimits(); /** * Set the AltVsTime Date according to the current Date * in the KStars main window. Currently, this is only * used in the ctor to initialize the Date. */ void showCurrentDate(); /** * @return a KStarsDateTime object constructed from the * current setting in the Date widget. */ KStarsDateTime getDate(); /** * Determine the time of sunset and sunrise for the current * date and location settings. Convert the times to doubles, * expressing the times as fractions of a full day. * Calls AVTPlotWidget::setSunRiseSetTimes() to send the * numbers to the plot widget. */ void computeSunRiseSetTimes(); /** * Parse a string as an epoch number. If the string can't * be parsed, return 2000.0. * @param eName the epoch string to be parsed * @return the epoch number */ double getEpoch(const QString &eName); /** * @short Add a SkyObject to the display. * Constructs a PLotObject representing the Alt-vs-time curve for the object. * @param o pointer to the SkyObject to be added * @param forceAdd if true, then the object will be added, even if there * is already a curve for the same coordinates. */ void processObject(SkyObject *o, bool forceAdd = false); /** * @short Determine the altitude coordinate of a SkyPoint, * given an hour of the day. * * This is called for every 30-minute interval in the displayed Day, * in order to construct the altitude curve for a given object. * @param p the skypoint whose altitude is to be found * @param hour the time in the displayed day, expressed in hours * @return the Altitude, expresse in degrees */ double findAltitude(SkyPoint *p, double hour); /** * @short get object name. If star has no name, generate a name based on catalog number. * @param translated set to true if the translated name is required. */ QString getObjectName(const SkyObject *o, bool translated = true); void drawGradient(); public slots: /** @short Update the plot to reflec new Date and Location settings. */ void slotUpdateDateLoc(); /** @short Clear the list of displayed objects. */ void slotClear(); /** @short Show information from the curve as a tooltip. */ void plotMousePress(QCPAbstractPlottable *abstractPlottable, int dataIndex, QMouseEvent *event); /** @short Update the X axis on Zoom and Drag. */ void onXRangeChanged(const QCPRange &range); /** @short Update the Y axis on Zoom and Drag. */ void onYRangeChanged(const QCPRange &range); /** @short Compute the altitude for a certain time. */ void slotComputeAltitudeByTime(); /** @short Mark the rise time on the curve. */ void slotMarkRiseTime(); /** @short Mark the set time on the curve. */ void slotMarkSetTime(); /** @short Mark the transit time on the curve. */ void slotMarkTransitTime(); /** @short Draw the white vertical line on click. */ void mouseOverLine(QMouseEvent *event); /** @short Clear the edit boxes for specifying a new object. */ void slotClearBoxes(); /** * @short Add an object to the list of displayed objects, according * to the data entered in the edit boxes. */ void slotAddSource(); /** * @short Launch the Find Object window to select a new object for * the list of displayed objects. */ void slotBrowseObject(); /** @short Launch the Location dialog to choose a new location. */ void slotChooseCity(); /** * @short Move input keyboard focus to the next logical widget. * We need a separate slot for this because we are intercepting * Enter key events, which close the window by default, to * advance input focus instead (when the Enter events occur in * certain Edit boxes). */ void slotAdvanceFocus(); /** * Update the plot to highlight the altitude curve of the objects * which is highlighted in the listbox. */ void slotHighlight(int); /** @short Print plot widget */ void slotPrint(); private: /** @short find start of dawn, end of dusk, maximum and minimum elevation of the sun */ void setDawnDusk(); AltVsTimeUI *avtUI { nullptr }; GeoLocation *geo { nullptr }; QList pList; QList deleteList; int DayOffset { 0 }; int minAlt { 0 }; int maxAlt { 0 }; QCPItemPixmap *background { nullptr }; QPixmap *gradient { nullptr }; }; diff --git a/kstars/tools/astrocalc.cpp b/kstars/tools/astrocalc.cpp index f7ceb6f38..cb1a4fae3 100644 --- a/kstars/tools/astrocalc.cpp +++ b/kstars/tools/astrocalc.cpp @@ -1,239 +1,239 @@ /*************************************************************************** astrocalc.cpp - description ------------------- begin : wed dec 19 16:20:11 CET 2002 copyright : (C) 2001-2005 by Pablo de Vicente email : p.devicente@wanadoo.es ***************************************************************************/ /*************************************************************************** * * * 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 "astrocalc.h" #include "modcalcjd.h" #include "modcalcgeodcoord.h" #include "modcalcgalcoord.h" #include "modcalcsidtime.h" #include "modcalcapcoord.h" #include "modcalcdaylength.h" #include "modcalcaltaz.h" #include "modcalcplanets.h" #include "modcalceclipticcoords.h" #include "modcalcangdist.h" #include "modcalcvizequinox.h" #include "modcalcvlsr.h" #include "conjunctions.h" #include #include #include #include #include #include AstroCalc::AstroCalc(QWidget *parent) : QDialog(parent) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif // List of messages. Maybe there is better place for it... QString message = i18n("" "

KStars Astrocalculator

" "

" "The KStars Astrocalculator contains several modules " "which perform a variety of astronomy-related calculations. " "The modules are organized into several categories: " "

    " "
  • Time calculators: " "Convert between time systems, and predict the timing of celestial events
  • " "
  • Coordinate converters: " "Convert between various coordinate systems
  • " "
  • Solar system: " "Predict the position of any planet, from a given location on Earth at a given time
  • " "
" "
"); QString messageTime = i18n("" "Section which includes algorithms for computing time ephemeris" "
  • " "Julian Day: Julian Day/Calendar conversion" "
  • " "Sidereal Time: Sidereal/Universal time conversion" "
  • " "Almanac: Rise/Set/Transit timing and position data " "for the Sun and Moon" "
  • " "Equinoxes & Solstices: Equinoxes, Solstices and duration of the " "seasons" "
" "
"); QString messageCoord = i18n("" "Section with algorithms for the conversion of " "different astronomical systems of coordinates" "
  • " "Galactic: Galactic/Equatorial coordinates conversion" "
  • " "Apparent: Computation of current equatorial coordinates" " from a given epoch" "
  • " "Ecliptic: Ecliptic/Equatorial coordinates conversion" "
  • " "Horizontal: Computation of azimuth and elevation for a " "given source, time, and location on the Earth" "
  • " "Angular Distance: Computation of angular distance between " "two objects whose positions are given in equatorial coordinates" "
  • " "Geodetic Coords: Geodetic/XYZ coordinate conversion" "
  • " "LSR Velocity: Computation of the heliocentric, geocentric " "and topocentric radial velocity of a source from its LSR velocity" "
" "
"); QString messageSolar = i18n("" "Section with algorithms regarding information " "on solar system bodies coordinates and times" "
  • " "Planets Coords: Coordinates for the planets, moon and sun " "at a given time and from a given position on Earth " "
" "
"); QSplitter *split = new QSplitter(this); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(split); setLayout(mainLayout); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); setWindowTitle(i18n("Calculator")); // Create navigation panel navigationPanel = new QTreeWidget(split); navigationPanel->setColumnCount(1); navigationPanel->setHeaderLabels(QStringList(i18n("Calculator modules"))); navigationPanel->setSortingEnabled(false); //FIXME: Would be better to make the navigationPanel fit its contents, //but I wasn't able to make it work navigationPanel->setMinimumWidth(200); acStack = new QStackedWidget(split); splashScreen = new QTextEdit(message, acStack); splashScreen->setReadOnly(true); //FIXME: Minimum size is set to resize calculator to correct size //when no modules is loaded. This is simply biggest size of //calculator modules. I think it should be set in more cleverly. splashScreen->setMinimumSize(640, 550); acStack->addWidget(splashScreen); // Load icons // JM 2016-10-02: Those are missing, disabling the icons for now /* QIcon jdIcon = QIcon ("jd.png"); QIcon geodIcon = QIcon ("geodetic.png"); QIcon solarIcon = QIcon ("geodetic.png"); // QIcon sunsetIcon = QIcon ("sunset.png"); // Its usage is commented out. QIcon timeIcon = QIcon ("sunclock.png");*/ /* Populate the tree widget and widget stack */ // Time-related entries QTreeWidgetItem *timeItem = addTreeTopItem(navigationPanel, i18n("Time Calculators"), messageTime); //timeItem->setIcon(0,timeIcon); //addTreeItem (timeItem, i18n("Julian Day"))->setIcon(0,jdIcon); addTreeItem(timeItem, i18n("Julian Day")); addTreeItem(timeItem, i18n("Sidereal Time")); addTreeItem(timeItem, i18n("Almanac")); addTreeItem(timeItem, i18n("Equinoxes & Solstices")); // dayItem->setIcon(0,sunsetIcon); // Coordinate-related entries QTreeWidgetItem *coordItem = addTreeTopItem(navigationPanel, i18n("Coordinate Converters"), messageCoord); addTreeItem(coordItem, i18n("Equatorial/Galactic")); addTreeItem(coordItem, i18n("Apparent Coordinates")); addTreeItem(coordItem, i18n("Horizontal Coordinates")); addTreeItem(coordItem, i18n("Ecliptic Coordinates")); addTreeItem(coordItem, i18n("Angular Distance")); addTreeItem(coordItem, i18n("Geodetic Coordinates")); addTreeItem(coordItem, i18n("LSR Velocity")); // Solar System related entries QTreeWidgetItem *solarItem = addTreeTopItem(navigationPanel, i18n("Solar System"), messageSolar); //solarItem->setIcon(0,solarIcon); addTreeItem(solarItem, i18n("Planets Coordinates")); addTreeItem(solarItem, i18n("Conjunctions")); acStack->setCurrentWidget(splashScreen); connect(navigationPanel, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(slotItemSelection(QTreeWidgetItem*))); } template QWidget *AstroCalc::addToStack() { T *t = new T(acStack); acStack->addWidget(t); return t; } template QTreeWidgetItem *AstroCalc::addTreeItem(QTreeWidgetItem *parent, const QString &title) { QTreeWidgetItem *item = new QTreeWidgetItem(parent, QStringList(title)); dispatchTable.insert(item, WidgetThunk(this, &AstroCalc::addToStack)); return item; } QTreeWidgetItem *AstroCalc::addTreeTopItem(QTreeWidget *parent, const QString &title, const QString &html) { QTreeWidgetItem *item = new QTreeWidgetItem(parent, QStringList(title)); htmlTable.insert(item, html); return item; } void AstroCalc::slotItemSelection(QTreeWidgetItem *item) { - if (item == 0) + if (item == nullptr) return; // Lookup in HTML table QMap::iterator iterHTML = htmlTable.find(item); if (iterHTML != htmlTable.end()) { splashScreen->setHtml(*iterHTML); acStack->setCurrentWidget(splashScreen); return; } // Lookup in frames table QMap::iterator iter = dispatchTable.find(item); if (iter != dispatchTable.end()) { acStack->setCurrentWidget(iter->eval()); } } QSize AstroCalc::sizeHint() const { return QSize(640, 430); } QWidget *AstroCalc::WidgetThunk::eval() { - if (widget == 0) + if (widget == nullptr) { // This is pointer to member function call. widget = (calc->*func)(); } return widget; } diff --git a/kstars/tools/astrocalc.h b/kstars/tools/astrocalc.h index 1050b62f2..9fd50ab9f 100644 --- a/kstars/tools/astrocalc.h +++ b/kstars/tools/astrocalc.h @@ -1,111 +1,111 @@ /*************************************************************************** astrocalc.h - description ------------------- begin : wed dec 19 16:20:11 CET 2002 copyright : (C) 2001-2002 by Pablo de Vicente email : vicente@oan.es ***************************************************************************/ /*************************************************************************** * * * 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 #include #include class QStackedWidget; class QTextEdit; class QTreeWidget; class QTreeWidgetItem; /** * @class AstroCalc is the base class for the KStars astronomical calculator * * @author: Pablo de Vicente * @version 0.9 */ class AstroCalc : public QDialog { Q_OBJECT public: explicit AstroCalc(QWidget *parent = nullptr); /** @return suggested size of calculator window. */ QSize sizeHint() const override; public slots: // Q: Why is this public when we don't have access to navigationPanel anyway? // Also doesn't seem to be used from outside -- asimha /** Display calculator module or help text based on item selected. */ void slotItemSelection(QTreeWidgetItem *it); private: /** Pointer to function which return QWidget */ typedef QWidget *(AstroCalc::*WidgetConstructor)(); /** * Data structure used for lazy widget construction. This class * construct widget when it requested. */ class WidgetThunk { public: /** * Create thunk * @param acalc pointer to class. * @param f function which construct widget. */ - WidgetThunk(AstroCalc *acalc, const WidgetConstructor& f) : widget(0), calc(acalc), func(f) { } + WidgetThunk(AstroCalc *acalc, const WidgetConstructor& f) : widget(nullptr), calc(acalc), func(f) { } /** * Request widget. * @return newly created widget or cached value. */ QWidget *eval(); private: /// Cached value QWidget *widget { nullptr }; /// Pointer to calculator AstroCalc *calc { nullptr }; /// Function call to construct the widget. WidgetConstructor func; }; /** * Create widget of type T and put it to widget stack. Widget must * have construtor of type T(QWidget*). Returns constructed widget. */ template inline QWidget *addToStack(); /** * Add top level item to navigation panel. At the same time adds item to htmlTable * @param title name of item * @param html string to be displayed in splash screen */ QTreeWidgetItem *addTreeTopItem(QTreeWidget *parent, const QString &title, const QString &html); /** * Add item to navigation panel. At the same time adds item to dispatchTable Template * parameter is type of widget to be constructed and added to widget stack. It must * have T() constructor. * @param title name of item */ template QTreeWidgetItem *addTreeItem(QTreeWidgetItem *parent, const QString &title); /** Lookup table for help texts. Maps navpanel item to help text. */ QMap htmlTable; /** Lookup table for widgets. Maps navpanel item to widget to be displayed. */ QMap dispatchTable; QTreeWidget *navigationPanel { nullptr }; QStackedWidget *acStack { nullptr }; QTextEdit *splashScreen { nullptr }; }; diff --git a/kstars/tools/calendarwidget.h b/kstars/tools/calendarwidget.h index aadb021f1..3373824c4 100644 --- a/kstars/tools/calendarwidget.h +++ b/kstars/tools/calendarwidget.h @@ -1,54 +1,54 @@ /*************************************************************************** calendarwidget.h - description ------------------- begin : Wed Jul 16 2008 copyright : (C) 2008 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. * * * ***************************************************************************/ #ifndef CALENDARWIDGET_H_ #define CALENDARWIDGET_H_ #include #include /** @class CalendarWidget *@short An extension of the KPlotWidget for the SkyCalendar tool. */ class CalendarWidget : public KPlotWidget { Q_OBJECT public: - explicit CalendarWidget(QWidget *parent = 0); + explicit CalendarWidget(QWidget *parent = nullptr); void setHorizon(); inline float getRiseTime(int i) { return riseTimeList.at(i); } inline float getSetTime(int i) { return setTimeList.at(i); } protected: void paintEvent(QPaintEvent *e) override; private: void drawHorizon(QPainter *p); void drawAxes(QPainter *p) override; QList dateList; QList riseTimeList; QList setTimeList; float minSTime; float maxRTime; QPolygonF polySunRise; QPolygonF polySunSet; }; #endif diff --git a/kstars/tools/conjunctions.cpp b/kstars/tools/conjunctions.cpp index ac159e533..8e98f95f4 100644 --- a/kstars/tools/conjunctions.cpp +++ b/kstars/tools/conjunctions.cpp @@ -1,409 +1,409 @@ /*************************************************************************** conjunctions.cpp - Conjunctions Tool ------------------- begin : Sun 20th Apr 2008 copyright : (C) 2008 Akarsh Simha email : akarshsimha@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. * * * ***************************************************************************/ /*************************************************************************** * * * Much of the code here is taken from Pablo de Vicente's * * modcalcplanets.cpp * * * ***************************************************************************/ #include "conjunctions.h" #include "geolocation.h" #include "ksconjunct.h" #include "kstars.h" #include "kstarsdata.h" #include "skymap.h" #include "dialogs/finddialog.h" #include "dialogs/locationdialog.h" #include "skycomponents/skymapcomposite.h" #include "skyobjects/kscomet.h" #include "skyobjects/kspluto.h" #include #include #include ConjunctionsTool::ConjunctionsTool(QWidget *parentSplit) : QFrame(parentSplit) { setupUi(this); KStarsData *kd = KStarsData::Instance(); KStarsDateTime dtStart(KStarsDateTime::currentDateTime()); KStarsDateTime dtStop(dtStart.djd() + 365.24); // TODO: Refine //startDate -> setDateTime( dtStart.dateTime() ); //stopDate -> setDateTime( dtStop.dateTime() ); //TODO Check if this change works startDate->setDateTime(dtStart); stopDate->setDateTime(dtStop); geoPlace = kd->geo(); LocationButton->setText(geoPlace->fullName()); // Init filter type combobox FilterTypeComboBox->addItem(i18n("Single Object...")); FilterTypeComboBox->addItem(i18n("Any")); FilterTypeComboBox->addItem(i18n("Stars")); FilterTypeComboBox->addItem(i18n("Solar System")); FilterTypeComboBox->addItem(i18n("Planets")); FilterTypeComboBox->addItem(i18n("Comets")); FilterTypeComboBox->addItem(i18n("Asteroids")); FilterTypeComboBox->addItem(i18n("Open Clusters")); FilterTypeComboBox->addItem(i18n("Globular Clusters")); FilterTypeComboBox->addItem(i18n("Gaseous Nebulae")); FilterTypeComboBox->addItem(i18n("Planetary Nebulae")); FilterTypeComboBox->addItem(i18n("Galaxies")); pNames[KSPlanetBase::MERCURY] = i18n("Mercury"); pNames[KSPlanetBase::VENUS] = i18n("Venus"); pNames[KSPlanetBase::MARS] = i18n("Mars"); pNames[KSPlanetBase::JUPITER] = i18n("Jupiter"); pNames[KSPlanetBase::SATURN] = i18n("Saturn"); pNames[KSPlanetBase::URANUS] = i18n("Uranus"); pNames[KSPlanetBase::NEPTUNE] = i18n("Neptune"); //pNames[KSPlanetBase::PLUTO] = i18n("Pluto"); pNames[KSPlanetBase::SUN] = i18n("Sun"); pNames[KSPlanetBase::MOON] = i18n("Moon"); for (int i = 0; i < KSPlanetBase::UNKNOWN_PLANET; ++i) { // Obj1ComboBox->insertItem( i, pNames[i] ); Obj2ComboBox->insertItem(i, pNames[i]); } // Initialize the Maximum Separation box to 1 degree maxSeparationBox->setDegType(true); maxSeparationBox->setDMS("01 00 00.0"); //Set up the Table Views m_Model = new QStandardItemModel(0, 5, this); m_Model->setHorizontalHeaderLabels(QStringList() << i18n("Conjunction/Opposition") << i18n("Date & Time (UT)") << i18n("Object 1") << i18n("Object 2") << i18n("Separation")); m_SortModel = new QSortFilterProxyModel(this); m_SortModel->setSourceModel(m_Model); OutputList->setModel(m_SortModel); OutputList->setSortingEnabled(true); OutputList->horizontalHeader()->setStretchLastSection(true); OutputList->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); OutputList->horizontalHeader()->resizeSection(2, 100); OutputList->horizontalHeader()->resizeSection(3, 100); OutputList->horizontalHeader()->resizeSection(4, 120); //is it bad way to fix default size of columns ? //FilterEdit->showClearButton = true; ClearFilterButton->setIcon(QIcon::fromTheme("edit-clear", QIcon(":/icons/breeze/default/edit-clear.svg"))); // signals and slots connections connect(LocationButton, SIGNAL(clicked()), this, SLOT(slotLocation())); connect(Obj1FindButton, SIGNAL(clicked()), this, SLOT(slotFindObject())); connect(ComputeButton, SIGNAL(clicked()), this, SLOT(slotCompute())); connect(FilterTypeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(slotFilterType(int))); connect(ClearButton, SIGNAL(clicked()), this, SLOT(slotClear())); connect(ExportButton, SIGNAL(clicked()), this, SLOT(slotExport())); connect(OutputList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotGoto())); connect(ClearFilterButton, SIGNAL(clicked()), FilterEdit, SLOT(clear())); connect(FilterEdit, SIGNAL(textChanged(QString)), this, SLOT(slotFilterReg(QString))); show(); } ConjunctionsTool::~ConjunctionsTool() { } void ConjunctionsTool::slotGoto() { int index = m_SortModel->mapToSource(OutputList->currentIndex()).row(); // Get the number of the line long double jd = outputJDList.value(index); KStarsDateTime dt; KStars *ks = KStars::Instance(); KStarsData *data = KStarsData::Instance(); SkyMap *map = ks->map(); // Show conjunction data->setLocation(*geoPlace); dt.setDJD(jd); data->changeDateTime(dt); map->setClickedObject(data->skyComposite()->findByName(m_Model->data(m_Model->index(index, 2)).toString())); map->setClickedPoint(map->clickedObject()); map->slotCenter(); } void ConjunctionsTool::slotFindObject() { QPointer fd = new FindDialog(KStars::Instance()); if (fd->exec() == QDialog::Accepted) { Object1.reset(); if (!fd->targetObject()) return; Object1.reset(fd->targetObject()->clone()); if (Object1.get() != nullptr) Obj1FindButton->setText(Object1->name()); } delete fd; } void ConjunctionsTool::slotLocation() { QPointer ld(new LocationDialog(this)); if (ld->exec() == QDialog::Accepted && ld) { geoPlace = ld->selectedCity(); LocationButton->setText(geoPlace->fullName()); } delete ld; } void ConjunctionsTool::slotFilterType(int) { // Disable find button if the user select an object type if (FilterTypeComboBox->currentIndex() == 0) Obj1FindButton->setEnabled(true); else Obj1FindButton->setEnabled(false); } void ConjunctionsTool::slotClear() { m_Model->setRowCount(0); outputJDList.clear(); m_index = 0; } void ConjunctionsTool::slotExport() { int i, j; QByteArray line; //QFile file( KFileDialog::getSaveFileName( QDir::homePath(), "*|All files", this, "Save Conjunctions" ) ); - QFile file(QFileDialog::getSaveFileName(0, i18n("Save Conjunctions"), QDir::homePath(), "*|All files")); + QFile file(QFileDialog::getSaveFileName(nullptr, i18n("Save Conjunctions"), QDir::homePath(), "*|All files")); file.open(QIODevice::WriteOnly | QIODevice::Text); for (i = 0; i < m_Model->rowCount(); ++i) { for (j = 0; j < m_Model->columnCount(); ++j) { line.append(m_Model->data(m_Model->index(i, j)).toByteArray()); if (j < m_Model->columnCount() - 1) line.append(";"); else line.append("\n"); } file.write(line); line.clear(); } file.close(); } void ConjunctionsTool::slotFilterReg(const QString &filter) { m_SortModel->setFilterRegExp(QRegExp(filter, Qt::CaseInsensitive, QRegExp::RegExp)); m_SortModel->setFilterKeyColumn(-1); } void ConjunctionsTool::slotCompute(void) { KStarsDateTime dtStart(startDate->dateTime()); // Start date KStarsDateTime dtStop(stopDate->dateTime()); // Stop date long double startJD = dtStart.djd(); // Start julian day long double stopJD = dtStop.djd(); // Stop julian day bool opposition = false; // true=opposition, false=conjunction if (Opposition->currentIndex()) opposition = true; QStringList objects; // List of sky object used as Object1 KStarsData *data = KStarsData::Instance(); int progress = 0; // Check if we have a valid angle in maxSeparationBox dms maxSeparation(0.0); bool ok; maxSeparation = maxSeparationBox->createDms(true, &ok); if (!ok) { - KMessageBox::sorry(0, i18n("Maximum separation entered is not a valid angle. Use the What's this help feature " + KMessageBox::sorry(nullptr, i18n("Maximum separation entered is not a valid angle. Use the What's this help feature " "for information on how to enter a valid angle")); return; } // Check if Object1 and Object2 are set if (FilterTypeComboBox->currentIndex() == 0 && Object1.get() == nullptr) { KMessageBox::sorry( - 0, i18n("Please select an object to check conjunctions with, by clicking on the \'Find Object\' button.")); + nullptr, i18n("Please select an object to check conjunctions with, by clicking on the \'Find Object\' button.")); return; } Object2.reset(KSPlanetBase::createPlanet(Obj2ComboBox->currentIndex())); if (FilterTypeComboBox->currentIndex() == 0 && Object1->name() == Object2->name()) { // FIXME: Must free the created Objects - KMessageBox::sorry(0, i18n("Please select two different objects to check conjunctions with.")); + KMessageBox::sorry(nullptr, i18n("Please select two different objects to check conjunctions with.")); return; } // Init KSConjunct object KSConjunct ksc; connect(&ksc, SIGNAL(madeProgress(int)), this, SLOT(showProgress(int))); ksc.setGeoLocation(geoPlace); switch (FilterTypeComboBox->currentIndex()) { case 1: // All object types foreach (int type, data->skyComposite()->objectNames().keys()) objects += data->skyComposite()->objectNames(type); break; case 2: // Stars objects += data->skyComposite()->objectNames(SkyObject::STAR); objects += data->skyComposite()->objectNames(SkyObject::CATALOG_STAR); break; case 3: // Solar system objects += data->skyComposite()->objectNames(SkyObject::PLANET); objects += data->skyComposite()->objectNames(SkyObject::COMET); objects += data->skyComposite()->objectNames(SkyObject::ASTEROID); objects += data->skyComposite()->objectNames(SkyObject::MOON); objects += i18n("Sun"); // Remove Object2 planet objects.removeAll(Object2->name()); break; case 4: // Planet objects += data->skyComposite()->objectNames(SkyObject::PLANET); // Remove Object2 planet objects.removeAll(Object2->name()); break; case 5: // Comet objects += data->skyComposite()->objectNames(SkyObject::COMET); break; case 6: // Ateroid objects += data->skyComposite()->objectNames(SkyObject::ASTEROID); break; case 7: // Open Clusters objects = data->skyComposite()->objectNames(SkyObject::OPEN_CLUSTER); break; case 8: // Open Clusters objects = data->skyComposite()->objectNames(SkyObject::GLOBULAR_CLUSTER); break; case 9: // Gaseous nebulae objects = data->skyComposite()->objectNames(SkyObject::GASEOUS_NEBULA); break; case 10: // Planetary nebula objects = data->skyComposite()->objectNames(SkyObject::PLANETARY_NEBULA); break; case 11: // Galaxies objects = data->skyComposite()->objectNames(SkyObject::GALAXY); break; } // Remove all Jupiter and Saturn moons // KStars crash if we compute a conjunction between a planet and one of this moon if (FilterTypeComboBox->currentIndex() == 1 || FilterTypeComboBox->currentIndex() == 3 || FilterTypeComboBox->currentIndex() == 6) { objects.removeAll("Io"); objects.removeAll("Europa"); objects.removeAll("Ganymede"); objects.removeAll("Callisto"); objects.removeAll("Mimas"); objects.removeAll("Enceladus"); objects.removeAll("Tethys"); objects.removeAll("Dione"); objects.removeAll("Rhea"); objects.removeAll("Titan"); objects.removeAll("Hyperion"); objects.removeAll("Iapetus"); } if (FilterTypeComboBox->currentIndex() != 0) { // Show a progress dialog while processing QProgressDialog progressDlg(i18n("Compute conjunction..."), i18n("Abort"), 0, objects.count(), this); progressDlg.setWindowModality(Qt::WindowModal); progressDlg.setValue(0); for (auto &object : objects) { // If the user click on the 'cancel' button if (progressDlg.wasCanceled()) break; // Update progress dialog ++progress; progressDlg.setValue(progress); progressDlg.setLabelText(i18n("Compute conjunction between %1 and %2", Object2->name(), object)); // Compute conjuction Object1.reset(data->skyComposite()->findByName(object)); showConjunctions(ksc.findClosestApproach(*Object1, *Object2, startJD, stopJD, maxSeparation, opposition), object, Object2->name()); } progressDlg.setValue(objects.count()); } else { // Change cursor while we search for conjunction QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); ComputeStack->setCurrentIndex(1); showConjunctions(ksc.findClosestApproach(*Object1, *Object2, startJD, stopJD, maxSeparation, opposition), Object1->name(), Object2->name()); ComputeStack->setCurrentIndex(0); // Restore cursor QApplication::restoreOverrideCursor(); } Object2.reset(); } void ConjunctionsTool::showProgress(int n) { progress->setValue(n); } void ConjunctionsTool::showConjunctions(const QMap &conjunctionlist, const QString &object1, const QString &object2) { KStarsDateTime dt; QList itemList; for (auto it = conjunctionlist.constBegin(); it != conjunctionlist.constEnd(); ++it) { dt.setDJD(it.key()); QStandardItem *typeItem; if (!Opposition->currentIndex()) typeItem = new QStandardItem(i18n("Conjunction")); else typeItem = new QStandardItem(i18n("Opposition")); itemList << typeItem //FIXME TODO is this ISO date? is there a ready format to use? //<< new QStandardItem( QLocale().toString( dt.dateTime(), "YYYY-MM-DDTHH:mm:SS" ) ) //<< new QStandardItem( QLocale().toString( dt, Qt::ISODate) ) << new QStandardItem(dt.toString(Qt::ISODate)) << new QStandardItem(object1) << new QStandardItem(object2) << new QStandardItem(it.value().toDMSString()); m_Model->appendRow(itemList); itemList.clear(); outputJDList.insert(m_index, it.key()); ++m_index; } } diff --git a/kstars/tools/exporteyepieceview.h b/kstars/tools/exporteyepieceview.h index af45b0e3b..86793db39 100644 --- a/kstars/tools/exporteyepieceview.h +++ b/kstars/tools/exporteyepieceview.h @@ -1,78 +1,78 @@ /*************************************************************************** exporteyepieceview.h - K Desktop Planetarium ------------------- begin : Sun 17 Jan 2016 21:36:25 CST copyright : (c) 2016 by Akarsh Simha email : akarsh.simha@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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 "kstarsdatetime.h" #include #include #include class QComboBox; class QLabel; class QPixmap; class SkyPoint; /** * @class ExportEyepieceView * @short Dialog to export the eyepiece view as an image, with some annotations for field-use * * @author Akarsh Simha */ class ExportEyepieceView : public QDialog { Q_OBJECT; public: /** * @short Constructor * @note Class self-destructs (commits suicide). Invoke and forget. */ ExportEyepieceView(const SkyPoint *_sp, const KStarsDateTime &dt, const QPixmap *renderImage, - const QPixmap *renderChart, QWidget *parent = 0); + const QPixmap *renderChart, QWidget *parent = nullptr); public slots: /** Change the tick overlay scheme */ void slotOverlayTicks(int overlayType); /** Save the image (export), and then close the dialog by calling slotCloseDialog() */ void slotSaveImage(); /** Closes the dialog, and sets up deleteLater() so that the dialog is destructed. */ void slotCloseDialog(); private slots: /** Render the output */ void render(); private: QLabel *m_outputDisplay { nullptr }; QLabel *m_tickWarningLabel { nullptr }; QComboBox *m_tickConfigCombo { nullptr }; KStarsDateTime m_dt; std::unique_ptr m_sp; std::unique_ptr m_renderImage; std::unique_ptr m_renderChart; QImage m_output; int m_tickConfig { 0 }; }; diff --git a/kstars/tools/eyepiecefield.cpp b/kstars/tools/eyepiecefield.cpp index 43eb5f764..c042349d7 100644 --- a/kstars/tools/eyepiecefield.cpp +++ b/kstars/tools/eyepiecefield.cpp @@ -1,635 +1,635 @@ /*************************************************************************** eyepiecefield.cpp - K Desktop Planetarium ------------------- begin : Fri 30 May 2014 15:07:46 CDT copyright : (c) 2014 by Akarsh Simha email : akarsh.simha@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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 "eyepiecefield.h" #include "exporteyepieceview.h" #include "fov.h" #include "ksdssdownloader.h" #include "kstars.h" #include "Options.h" #include "skymap.h" #include "skyqpainter.h" #include #include #include #include #include #include #include #include #include #include #include EyepieceField::EyepieceField(QWidget *parent) : QDialog(parent) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif setWindowTitle(i18n("Eyepiece Field View")); - m_sp = 0; - m_dt = 0; - m_currentFOV = 0; + m_sp = nullptr; + m_dt = nullptr; + m_currentFOV = nullptr; m_fovWidth = m_fovHeight = 0; - m_dler = 0; + m_dler = nullptr; QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(mainWidget); setLayout(mainLayout); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); buttonBox->addButton(i18nc("Export image", "Export"), QDialogButtonBox::AcceptRole); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(slotExport())); QVBoxLayout *rows = new QVBoxLayout; mainWidget->setLayout(rows); m_skyChartDisplay = new QLabel; m_skyChartDisplay->setBackgroundRole(QPalette::Base); m_skyChartDisplay->setScaledContents(false); m_skyChartDisplay->setMinimumWidth(400); m_skyImageDisplay = new QLabel; m_skyImageDisplay->setBackgroundRole(QPalette::Base); m_skyImageDisplay->setScaledContents(false); m_skyImageDisplay->setMinimumWidth(400); m_skyImageDisplay->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); QHBoxLayout *imageLayout = new QHBoxLayout; rows->addLayout(imageLayout); imageLayout->addWidget(m_skyChartDisplay); imageLayout->addWidget(m_skyImageDisplay); m_invertView = new QCheckBox(i18n("Invert view"), this); m_flipView = new QCheckBox(i18n("Flip view"), this); m_overlay = new QCheckBox(i18n("Overlay"), this); m_invertColors = new QCheckBox(i18n("Invert colors"), this); m_getDSS = new QPushButton(i18n("Fetch DSS image"), this); m_getDSS->setVisible(false); QHBoxLayout *optionsLayout = new QHBoxLayout; optionsLayout->addWidget(m_invertView); optionsLayout->addWidget(m_flipView); optionsLayout->addStretch(); optionsLayout->addWidget(m_overlay); optionsLayout->addWidget(m_invertColors); optionsLayout->addWidget(m_getDSS); rows->addLayout(optionsLayout); m_rotationSlider = new QSlider(Qt::Horizontal, this); m_rotationSlider->setMaximum(180); m_rotationSlider->setMinimum(-180); m_rotationSlider->setTickInterval(30); m_rotationSlider->setPageStep(30); QLabel *sliderLabel = new QLabel(i18n("Rotation: "), this); m_presetCombo = new QComboBox(this); m_presetCombo->addItem(i18n("None")); m_presetCombo->addItem(i18n("Vanilla")); m_presetCombo->addItem(i18n("Flipped")); m_presetCombo->addItem(i18n("Refractor")); m_presetCombo->addItem(i18n("Dobsonian")); QLabel *presetLabel = new QLabel(i18n("Preset: "), this); QHBoxLayout *rotationLayout = new QHBoxLayout; rotationLayout->addWidget(sliderLabel); rotationLayout->addWidget(m_rotationSlider); rotationLayout->addWidget(presetLabel); rotationLayout->addWidget(m_presetCombo); rows->addLayout(rotationLayout); connect(m_invertView, SIGNAL(stateChanged(int)), this, SLOT(render())); connect(m_flipView, SIGNAL(stateChanged(int)), this, SLOT(render())); connect(m_invertColors, SIGNAL(stateChanged(int)), this, SLOT(render())); connect(m_overlay, SIGNAL(stateChanged(int)), this, SLOT(render())); connect(m_rotationSlider, SIGNAL(valueChanged(int)), this, SLOT(render())); connect(m_presetCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotEnforcePreset(int))); connect(m_presetCombo, SIGNAL(activated(int)), this, SLOT(slotEnforcePreset(int))); connect(m_getDSS, SIGNAL(clicked()), this, SLOT(slotDownloadDss())); } void EyepieceField::slotEnforcePreset(int index) { if (index == -1) index = m_presetCombo->currentIndex(); if (index == -1) index = 0; if (index == 0) return; // Preset "None" makes no changes double altAzRot = (m_usedAltAz ? 0.0 : findNorthAngle(m_sp, KStarsData::Instance()->geo()->lat()).Degrees()); if (altAzRot > 180.0) altAzRot -= 360.0; double dobRot = altAzRot - m_sp->alt().Degrees(); // set rotation to altitude CW if (dobRot > 180.0) dobRot -= 360.0; if (dobRot < -180.0) dobRot += 360.0; switch (index) { case 1: // Preset vanilla m_rotationSlider->setValue(0.0); // reset rotation m_invertView->setChecked(false); // reset inversion m_flipView->setChecked(false); // reset flip break; case 2: // Preset flipped m_rotationSlider->setValue(0.0); // reset rotation m_invertView->setChecked(false); // reset inversion m_flipView->setChecked(true); // set flip break; case 3: // Preset refractor m_rotationSlider->setValue(altAzRot); m_invertView->setChecked(true); m_flipView->setChecked(false); break; case 4: // Preset Dobsonian m_rotationSlider->setValue(dobRot); // set rotation for dob m_invertView->setChecked(true); // set inversion m_flipView->setChecked(false); break; default: break; } } void EyepieceField::showEyepieceField(SkyPoint *sp, FOV const *const fov, const QString &imagePath) { double fovWidth, fovHeight; Q_ASSERT(sp); // See if we were supplied a sky image; if so, load its metadata // Set up the new sky map FOV and pointing. full map FOV = 4 times the given FOV. if (fov) { fovWidth = fov->sizeX(); fovHeight = fov->sizeY(); } else if (QFile::exists(imagePath)) { fovWidth = fovHeight = -1.0; // figure out from the image. } else { //Q_ASSERT( false ); // Don't crash the program - KMessageBox::error(0, i18n(("No image found. Please specify the exact FOV."))); + KMessageBox::error(nullptr, i18n(("No image found. Please specify the exact FOV."))); return; } showEyepieceField(sp, fovWidth, fovHeight, imagePath); m_currentFOV = fov; } void EyepieceField::showEyepieceField(SkyPoint *sp, const double fovWidth, double fovHeight, const QString &imagePath) { if (m_skyChart.get() == nullptr) m_skyChart.reset(new QImage()); if (QFile::exists(imagePath)) { qCDebug(KSTARS) << "Image path " << imagePath << " exists"; if (m_skyImage.get() == nullptr) { qCDebug(KSTARS) << "Sky image did not exist, creating."; m_skyImage.reset(new QImage()); } } else { m_skyImage.reset(); } m_usedAltAz = Options::useAltAz(); generateEyepieceView(sp, m_skyChart.get(), m_skyImage.get(), fovWidth, fovHeight, imagePath); // Keep a copy for local purposes (computation of field rotation etc.) if (m_sp != sp) { if (m_sp) delete m_sp; m_sp = new SkyPoint(*sp); } // Update our date/time delete m_dt; m_dt = new KStarsDateTime(KStarsData::Instance()->ut()); // Enforce preset as per selection, since we have loaded a new eyepiece view slotEnforcePreset(-1); // Render the display render(); m_fovWidth = fovWidth; m_fovHeight = fovHeight; - m_currentFOV = 0; + m_currentFOV = nullptr; } void EyepieceField::generateEyepieceView(SkyPoint *sp, QImage *skyChart, QImage *skyImage, const FOV *fov, const QString &imagePath) { if (fov) { generateEyepieceView(sp, skyChart, skyImage, fov->sizeX(), fov->sizeY(), imagePath); } else { generateEyepieceView(sp, skyChart, skyImage, -1.0, -1.0, imagePath); } } void EyepieceField::generateEyepieceView(SkyPoint *sp, QImage *skyChart, QImage *skyImage, double fovWidth, double fovHeight, const QString &imagePath) { SkyMap *map = SkyMap::Instance(); KStars *ks = KStars::Instance(); Q_ASSERT(sp); Q_ASSERT(map); Q_ASSERT(ks); Q_ASSERT(skyChart); if (!skyChart) return; if (!map) // Requires initialization of Sky map. return; if (fovWidth <= 0) { if (!QFile::exists(imagePath)) return; // Otherwise, we will assume that the user wants the FOV of the image and we'll try to guess it from there } if (fovHeight <= 0) fovHeight = fovWidth; // Get DSS image width / height double dssWidth = 0, dssHeight = 0; if (QFile::exists(imagePath)) { KSDssImage dssImage(imagePath); dssWidth = dssImage.getMetadata().width; dssHeight = dssImage.getMetadata().height; if (!dssImage.getMetadata().isValid() || dssWidth == 0 || dssHeight == 0) { // Metadata unavailable, guess based on most common DSS arcsec/pixel //const double dssArcSecPerPixel = 1.01; dssWidth = dssImage.getImage().width() * 1.01 / 60.0; dssHeight = dssImage.getImage().height() * 1.01 / 60.0; } qCDebug(KSTARS) << "DSS width: " << dssWidth << " height: " << dssHeight; } // Set FOV width/height from DSS if necessary if (fovWidth <= 0) { fovWidth = dssWidth; fovHeight = dssHeight; } // Grab the sky chart // Save the current state of the sky map SkyPoint *oldFocus = map->focus(); double oldZoomFactor = Options::zoomFactor(); // Set the right zoom ks->setApproxFOV(((fovWidth > fovHeight) ? fovWidth : fovHeight) / 15.0); // map->setFocus( sp ); // FIXME: Why does setFocus() need a non-const SkyPoint pointer? KStarsData *const data = KStarsData::Instance(); sp->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false); map->setClickedPoint(sp); map->slotCenter(); qApp->processEvents(); // Repeat -- dirty workaround for some problem in KStars map->setClickedPoint(sp); map->slotCenter(); qApp->processEvents(); // determine screen arcminutes per pixel value const double arcMinToScreen = dms::PI * Options::zoomFactor() / 10800.0; // Vector export QTemporaryFile myTempSvgFile; myTempSvgFile.open(); // export as SVG QSvgGenerator svgGenerator; svgGenerator.setFileName(myTempSvgFile.fileName()); // svgGenerator.setTitle(i18n("")); // svgGenerator.setDescription(i18n("")); svgGenerator.setSize(QSize(map->width(), map->height())); svgGenerator.setResolution(qMax(map->logicalDpiX(), map->logicalDpiY())); svgGenerator.setViewBox(QRect(map->width() / 2.0 - arcMinToScreen * fovWidth / 2.0, map->height() / 2.0 - arcMinToScreen * fovHeight / 2.0, arcMinToScreen * fovWidth, arcMinToScreen * fovHeight)); SkyQPainter painter(KStars::Instance(), &svgGenerator); painter.begin(); map->exportSkyImage(&painter); painter.end(); // Render SVG file on raster QImage canvas QSvgRenderer svgRenderer(myTempSvgFile.fileName()); QImage *mySkyChart = new QImage(arcMinToScreen * fovWidth * 2.0, arcMinToScreen * fovHeight * 2.0, QImage::Format_ARGB32); // 2 times bigger in both dimensions. QPainter p2(mySkyChart); svgRenderer.render(&p2); p2.end(); *skyChart = *mySkyChart; delete mySkyChart; myTempSvgFile.close(); // Reset the sky-map map->setZoomFactor(oldZoomFactor); map->setClickedPoint(oldFocus); map->slotCenter(); qApp->processEvents(); // Repeat -- dirty workaround for some problem in KStars map->setZoomFactor(oldZoomFactor); map->setClickedPoint(oldFocus); map->slotCenter(); qApp->processEvents(); map->forceUpdate(); // Prepare the sky image if (QFile::exists(imagePath) && skyImage) { QImage *mySkyImage = new QImage(int(arcMinToScreen * fovWidth * 2.0), int(arcMinToScreen * fovHeight * 2.0), QImage::Format_ARGB32); mySkyImage->fill(Qt::transparent); QPainter p(mySkyImage); QImage rawImg(imagePath); if (rawImg.isNull()) { qWarning() << "Image constructed from " << imagePath << "is a null image! Are you sure you supplied an image file? Continuing nevertheless..."; } QImage img = rawImg.scaled(arcMinToScreen * dssWidth * 2.0, arcMinToScreen * dssHeight * 2.0, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); const auto ksd = KStarsData::Instance(); sp->updateCoordsNow(ksd->updateNum()); if (Options::useAltAz()) { // Need to rotate the image so that up is towards zenith rather than north. sp->EquatorialToHorizontal(ksd->lst(), ksd->geo()->lat()); dms northBearing = findNorthAngle(sp, ksd->geo()->lat()); qCDebug(KSTARS) << "North angle = " << northBearing.toDMSString(); QTransform transform; transform.rotate(northBearing.Degrees()); img = img.transformed(transform, Qt::SmoothTransformation); } p.drawImage( QPointF(mySkyImage->width() / 2.0 - img.width() / 2.0, mySkyImage->height() / 2.0 - img.height() / 2.0), img); p.end(); *skyImage = *mySkyImage; delete mySkyImage; } } void EyepieceField::renderEyepieceView(const QImage *skyChart, QPixmap *renderChart, const double rotation, const double scale, const bool flip, const bool invert, const QImage *skyImage, QPixmap *renderImage, const bool overlay, const bool invertColors) { QTransform transform; bool deleteRenderImage = false; transform.rotate(rotation); if (flip) transform.scale(-1, 1); if (invert) transform.scale(-1, -1); transform.scale(scale, scale); Q_ASSERT(skyChart && renderChart); if (!skyChart || !renderChart) return; *renderChart = QPixmap::fromImage(skyChart->transformed(transform, Qt::SmoothTransformation)); if (skyImage) { Q_ASSERT(overlay || renderImage); // in debug mode, check for calls that supply skyImage but not renderImage } if (overlay && !renderImage) { renderImage = new QPixmap(); // temporary, used for rendering skymap before overlay is done. deleteRenderImage = true; // we created it, so we must delete it. } if (skyImage && renderImage) { if (skyImage->isNull()) qWarning() << "Sky image supplied to renderEyepieceView() for rendering is a Null image!"; QImage i; i = skyImage->transformed(transform, Qt::SmoothTransformation); if (invertColors) i.invertPixels(); *renderImage = QPixmap::fromImage(i); } if (overlay && skyImage) { QColor skyColor = KStarsData::Instance()->colorScheme()->colorNamed("SkyColor"); QBitmap mask = QBitmap::fromImage( skyChart->createMaskFromColor(skyColor.rgb()).transformed(transform, Qt::SmoothTransformation)); renderChart->setMask(mask); QPainter p(renderImage); p.drawImage(QPointF(renderImage->width() / 2.0 - renderChart->width() / 2.0, renderImage->height() / 2.0 - renderChart->height() / 2.0), renderChart->toImage()); QPixmap temp(renderImage->width(), renderImage->height()); temp.fill(skyColor); QPainter p2(&temp); p2.drawImage(QPointF(0, 0), renderImage->toImage()); p2.end(); p.end(); *renderChart = *renderImage = temp; } if (deleteRenderImage) delete renderImage; } void EyepieceField::renderEyepieceView(SkyPoint *sp, QPixmap *renderChart, double fovWidth, double fovHeight, const double rotation, const double scale, const bool flip, const bool invert, const QString &imagePath, QPixmap *renderImage, const bool overlay, const bool invertColors) { - QImage *skyChart, *skyImage = 0; + QImage *skyChart, *skyImage = nullptr; skyChart = new QImage(); if (QFile::exists(imagePath) && (renderImage || overlay)) skyImage = new QImage(); generateEyepieceView(sp, skyChart, skyImage, fovWidth, fovHeight, imagePath); renderEyepieceView(skyChart, renderChart, rotation, scale, flip, invert, skyImage, renderImage, overlay, invertColors); delete skyChart; delete skyImage; } void EyepieceField::render() { double rotation = m_rotationSlider->value(); bool flip = m_flipView->isChecked(); bool invert = m_invertView->isChecked(); bool invertColors = m_invertColors->isChecked(); bool overlay = m_overlay->isChecked() && m_skyImage.get(); Q_ASSERT(m_skyChart.get()); renderEyepieceView(m_skyChart.get(), &m_renderChart, rotation, 1.0, flip, invert, m_skyImage.get(), &m_renderImage, overlay, invertColors); m_skyChartDisplay->setVisible(!overlay); if (m_skyImage.get() != nullptr) { m_skyImageDisplay->setVisible(true); m_overlay->setVisible(true); m_invertColors->setVisible(true); m_getDSS->setVisible(false); } else { m_skyImageDisplay->setVisible(false); m_overlay->setVisible(false); m_invertColors->setVisible(false); m_getDSS->setVisible(true); } if (!overlay) m_skyChartDisplay->setPixmap(m_renderChart.scaled(m_skyChartDisplay->width(), m_skyChartDisplay->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); if (m_skyImage.get() != nullptr) m_skyImageDisplay->setPixmap(m_renderImage.scaled(m_skyImageDisplay->width(), m_skyImageDisplay->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); update(); show(); } void EyepieceField::slotDownloadDss() { double fovWidth = 0, fovHeight = 0; - if (m_fovWidth == 0 && m_currentFOV == 0) + if (m_fovWidth == 0 && m_currentFOV == nullptr) { fovWidth = fovHeight = 15.0; } else if (m_currentFOV) { fovWidth = m_currentFOV->sizeX(); fovHeight = m_currentFOV->sizeY(); } if (!m_dler) { m_dler = new KSDssDownloader(this); connect(m_dler, SIGNAL(downloadComplete(bool)), SLOT(slotDssDownloaded(bool))); } KSDssImage::Metadata md; m_tempFile.open(); QUrl srcUrl = QUrl(KSDssDownloader::getDSSURL(m_sp, fovWidth, fovHeight, "all", &md)); m_dler->startSingleDownload(srcUrl, m_tempFile.fileName(), md); m_tempFile.close(); } void EyepieceField::slotDssDownloaded(bool success) { if (!success) { - KMessageBox::sorry(0, i18n("Failed to download DSS/SDSS image!")); + KMessageBox::sorry(nullptr, i18n("Failed to download DSS/SDSS image!")); return; } else showEyepieceField(m_sp, m_fovWidth, m_fovHeight, m_tempFile.fileName()); } void EyepieceField::slotExport() { bool overlay = m_overlay->isChecked() && m_skyImage.get(); new ExportEyepieceView(m_sp, *m_dt, ((m_skyImage.get() && !overlay) ? &m_renderImage : nullptr), &m_renderChart, this); } dms EyepieceField::findNorthAngle(const SkyPoint *sp, const dms *lat) { Q_ASSERT(sp && lat); // NOTE: northAngle1 is the correction due to lunisolar precession // (needs testing and checking). northAngle2 is the correction due // to going from equatorial to horizontal coordinates. // FIXME: The following code is a guess at how to handle // precession. While it might work in many cases, it might fail in // some. Careful testing will be needed to ensure that all // conditions are met, esp. with getting the signs right when // using arccosine! Nutation and planetary precession corrections // have not been included. -- asimha // TODO: Look at the Meeus book and see if it has some formulas -- asimha const double equinoxPrecessionPerYear = (50.35 / 3600.0); // Equinox precession in ecliptic longitude per year in degrees (ref: http://star-www.st-and.ac.uk/~fv/webnotes/chapt16.htm) dms netEquinoxPrecession(((sp->getLastPrecessJD() - J2000) / 365.25) * equinoxPrecessionPerYear); double cosNorthAngle1 = (netEquinoxPrecession.cos() - sp->dec0().sin() * sp->dec().sin()) / (sp->dec0().cos() * sp->dec().cos()); double northAngle1 = acos(cosNorthAngle1); if (sp->getLastPrecessJD() < J2000) northAngle1 = -northAngle1; if (sp->dec0().Degrees() < 0) northAngle1 = -northAngle1; // We trust that EquatorialToHorizontal has been called on sp, after all, how else can it have an alt/az representation. // Use spherical cosine rule (the triangle with vertices at sp, zenith and NCP) to compute the angle between direction of increasing altitude and north double cosNorthAngle2 = (lat->sin() - sp->alt().sin() * sp->dec().sin()) / (sp->alt().cos() * sp->dec().cos()); double northAngle2 = acos(cosNorthAngle2); // arccosine is blind to sign of the angle if (sp->az().reduce().Degrees() < 180.0) // if on the eastern hemisphere, flip sign northAngle2 = -northAngle2; double northAngle = northAngle1 + northAngle2; qCDebug(KSTARS) << "Data: alt = " << sp->alt().toDMSString() << "; az = " << sp->az().toDMSString() << "; ra, dec (" << sp->getLastPrecessJD() / 365.25 << ") = " << sp->ra().toHMSString() << "," << sp->dec().toDMSString() << "; ra0,dec0 (J2000.0) = " << sp->ra0().toHMSString() << "," << sp->dec0().toDMSString(); qCDebug(KSTARS) << "PA corrections: precession cosine = " << cosNorthAngle1 << "; angle = " << northAngle1 << "; horizontal = " << cosNorthAngle2 << "; angle = " << northAngle2; return dms(northAngle * 180 / M_PI); } diff --git a/kstars/tools/eyepiecefield.h b/kstars/tools/eyepiecefield.h index c3355b367..19852fc3c 100644 --- a/kstars/tools/eyepiecefield.h +++ b/kstars/tools/eyepiecefield.h @@ -1,182 +1,182 @@ /*************************************************************************** eyepiecefield.h - K Desktop Planetarium ------------------- begin : Fri 30 May 2014 14:39:37 CDT copyright : (c) 2014 by Akarsh Simha email : akarsh.simha@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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 "dms.h" #include #include #include #include class QCheckBox; class QComboBox; class QImage; class QLabel; class QPushButton; class QSlider; class QString; class FOV; class KSDssDownloader; class KStarsDateTime; class SkyPoint; /** * @class EyepieceField * @short Renders the view through the eyepiece of various telescope types * * @author Akarsh Simha */ class EyepieceField : public QDialog // FIXME: Rename to EyepieceView { Q_OBJECT public: /** Constructor */ explicit EyepieceField(QWidget *parent = nullptr); /** * @short Show the eyepiece field dialog * @param sp Sky point to draw the eyepiece field around. * @param fov Pointer to the FOV object describing the field of view. If no pointer is * provided, tries to get from image. If no image is provided, assumes 1 degree. * @param imagePath Optional path to DSS or other image. North should be on the top of the image. * @note The SkyPoint must have correct Alt/Az coordinates, maybe by calling update() * already before calling this method. */ - void showEyepieceField(SkyPoint *sp, FOV const *const fov = 0, const QString &imagePath = QString()); + void showEyepieceField(SkyPoint *sp, FOV const *const fov = nullptr, const QString &imagePath = QString()); /** * @short Show the eyepiece field dialog * @param sp Sky point to draw the eyepiece field around. * @param fovWidth width of field-of-view in arcminutes * @param fovHeight height of field-of-view in arcminutes (if not supplied, is set to fovWidth) * @param imagePath Optional path to DSS or other image. North should be on the top of the image. * @note The SkyPoint must have correct Alt/Az coordinates, maybe by calling update() already * before calling this method. */ void showEyepieceField(SkyPoint *sp, const double fovWidth, double fovHeight = -1.0, const QString &imagePath = QString()); /** * @short Generate the eyepiece field view and corresponding image view * @param sp Sky point to draw the render the eyepiece field around * @param skyChart A non-null pointer to replace with the eyepiece field image * @param skyImage An optionally non-null pointer to replace with the re-oriented sky image * @param fovWidth width of the field-of-view in arcminutes * @param fovHeight height of field-of-view in arcminutes (if not supplied, is set to fovWidth) * @param imagePath Optional path to DSS or other image. North * should be on the top of the image, and the size should be in * the metadata; otherwise 1.01 arcsec/pixel is assumed. * @note fovWidth can be zero/negative if imagePath is non-empty. If it is, the image * size is used for the FOV. * @note fovHeight can be zero/negative. If it is, fovWidth will be used. If fovWidth is also * zero, image size is used. */ static void generateEyepieceView(SkyPoint *sp, QImage *skyChart, QImage *skyImage = nullptr, double fovWidth = -1.0, double fovHeight = -1.0, const QString &imagePath = QString()); /** * @short Overloaded method provided for convenience. Obtains fovWidth/fovHeight from * FOV if non-null, else uses image */ static void generateEyepieceView(SkyPoint *sp, QImage *skyChart, QImage *skyImage = nullptr, const FOV *fov = nullptr, const QString &imagePath = QString()); /** * @short Orients the eyepiece view as needed, performs overlaying etc. * @param skyChart image which contains the sky chart, possibly generated using generateEyepieceView * @param skyImage optional image which contains the sky image, possibly generated using generateEyepieceView * @param renderChart pixmap onto which the sky chart is to be rendered * @param renderImage optional pixmap onto which the sky image is to be rendered * @param rotation optional, number of degrees by which to rotate the image(s) * @param scale optional, factor by which to scale the image(s) * @param flip optional, if true, the image is mirrored horizontally * @param invert optional, if true, the image is inverted, i.e. rotated by 180 degrees * @param overlay optional, if true, the sky image is overlaid on the sky map * @param invertColors optional, if true, the sky image is color-inverted */ static void renderEyepieceView(const QImage *skyChart, QPixmap *renderChart, const double rotation = 0, const double scale = 1.0, const bool flip = false, const bool invert = false, const QImage *skyImage = nullptr, QPixmap *renderImage = nullptr, const bool overlay = false, const bool invertColors = false); /** * @short Convenience method that generates and the renders the eyepiece view * @note calls generateEyepieceView() followed by the raw form of renderEyepieceView() to render an eyepiece view */ static void renderEyepieceView(SkyPoint *sp, QPixmap *renderChart, double fovWidth = -1.0, double fovHeight = -1.0, const double rotation = 0, const double scale = 1.0, const bool flip = false, const bool invert = false, const QString &imagePath = QString(), QPixmap *renderImage = nullptr, const bool overlay = false, const bool invertColors = false); /** * @short Finds the angle between "up" (i.e. direction of increasing altitude) and "north" (i.e. direction of increasing declination) at a given point in the sky * @fixme Procedure does not account for precession and nutation at the moment * @note SkyPoint must already have Equatorial and Horizontal coordinate synced */ static dms findNorthAngle(const SkyPoint *sp, const dms *lat); public slots: /** * @short Re-renders the view * Takes care of things like inverting colors, inverting orientation, flipping, rotation * @note Calls the static method renderEyepieceView to set things up */ void render(); /** Enforces a preset setting */ void slotEnforcePreset(int index = -1); /** Save image */ void slotExport(); private slots: /** Downloads a DSS image */ void slotDownloadDss(); /** Loads a downloaded DSS image */ void slotDssDownloaded(bool success); private: QLabel *m_skyChartDisplay { nullptr }; QLabel *m_skyImageDisplay { nullptr }; std::unique_ptr m_skyChart; std::unique_ptr m_skyImage; QSlider *m_rotationSlider { nullptr }; QCheckBox *m_invertColors { nullptr }; QCheckBox *m_overlay { nullptr }; QCheckBox *m_invertView { nullptr }; QCheckBox *m_flipView { nullptr }; QComboBox *m_presetCombo { nullptr }; QPushButton *m_getDSS { nullptr }; const FOV *m_currentFOV; double m_fovWidth { 0 }; double m_fovHeight { 0 }; KSDssDownloader *m_dler { nullptr }; KStarsDateTime *m_dt { nullptr }; SkyPoint *m_sp { nullptr }; double m_lat { 0 }; QTemporaryFile m_tempFile; QPixmap m_renderImage, m_renderChart; bool m_usedAltAz { false }; }; diff --git a/kstars/tools/flagmanager.cpp b/kstars/tools/flagmanager.cpp index a889fb3fc..8316996fd 100644 --- a/kstars/tools/flagmanager.cpp +++ b/kstars/tools/flagmanager.cpp @@ -1,369 +1,369 @@ /*************************************************************************** flagmanager.cpp - Flags manager ------------------- begin : Mon Feb 01 2009 copyright : (C) 2009 by Jerome SONRIER email : jsid@emor3j.fr.eu.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 "flagmanager.h" #include "config-kstars.h" #include "kspaths.h" #include "kstars.h" #include "kstarsdata.h" #include "Options.h" #include "skymap.h" #include "skycomponents/flagcomponent.h" #include "skycomponents/skymapcomposite.h" #ifdef HAVE_INDI #include #include "indi/indilistener.h" #include "indi/indistd.h" #include "indi/driverinfo.h" #endif #include #include #include FlagManagerUI::FlagManagerUI(QWidget *p) : QFrame(p) { setupUi(this); } FlagManager::FlagManager(QWidget *ks) : QDialog(ks) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif QList itemList; QList imageList; QStringList flagNames; int i; ui = new FlagManagerUI(this); setWindowTitle(i18n("Flag manager")); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(ui); setLayout(mainLayout); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); m_Ks = KStars::Instance(); ui->hintLabel->setText(i18n("To add custom icons, just add images in %1. File names must begin with flag. " "For example, the file flagSmall_red_cross.gif will be shown as Small red " "cross in the combo box.", KSPaths::writableLocation(QStandardPaths::GenericDataLocation))); //Set up the Table Views m_Model = new QStandardItemModel(0, 5, this); m_Model->setHorizontalHeaderLabels(QStringList() << i18nc("Right Ascension", "RA") << i18nc("Declination", "Dec") << i18n("Epoch") << i18n("Icon") << i18n("Label")); m_SortModel = new QSortFilterProxyModel(this); m_SortModel->setSourceModel(m_Model); m_SortModel->setDynamicSortFilter(true); ui->flagList->setModel(m_SortModel); ui->flagList->horizontalHeader()->setStretchLastSection(true); ui->flagList->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); ui->saveButton->setEnabled(false); //Fill the list imageList = m_Ks->data()->skyComposite()->flags()->imageList(); flagNames = m_Ks->data()->skyComposite()->flags()->getNames(); FlagComponent *flags = m_Ks->data()->skyComposite()->flags(); QPixmap pixmap; for (i = 0; i < m_Ks->data()->skyComposite()->flags()->size(); ++i) { QStandardItem *labelItem = new QStandardItem(flags->label(i)); labelItem->setForeground(QBrush(flags->labelColor(i))); itemList << new QStandardItem(flags->pointList().at(i)->ra0().toHMSString()) << new QStandardItem(flags->pointList().at(i)->dec0().toDMSString()) << new QStandardItem(flags->epoch(i)) << new QStandardItem(QIcon(pixmap.fromImage(flags->image(i))), flags->imageName(i)) << labelItem; m_Model->appendRow(itemList); itemList.clear(); } //Fill the combobox for (i = 0; i < imageList.size(); ++i) { ui->flagCombobox->addItem(QIcon(pixmap.fromImage(flags->imageList(i))), flagNames.at(i), flagNames.at(i)); } //Connect buttons connect(ui->addButton, SIGNAL(clicked()), this, SLOT(slotAddFlag())); connect(ui->delButton, SIGNAL(clicked()), this, SLOT(slotDeleteFlag())); connect(ui->CenterButton, SIGNAL(clicked()), this, SLOT(slotCenterFlag())); connect(ui->ScopeButton, SIGNAL(clicked()), this, SLOT(slotCenterTelescope())); connect(ui->flagList, SIGNAL(clicked(QModelIndex)), this, SLOT(slotSetShownFlag(QModelIndex))); connect(ui->flagList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotCenterFlag())); connect(ui->saveButton, SIGNAL(clicked()), this, SLOT(slotSaveChanges())); } FlagManager::~FlagManager() { } void FlagManager::setRaDec(const dms &ra, const dms &dec) { ui->raBox->show(ra, false); ui->decBox->show(dec, true); } void FlagManager::clearFields() { ui->raBox->clear(); ui->decBox->clear(); ui->epochBox->setText("2000.0"); ui->flagLabel->clear(); ui->flagLabel->setFocus(); //disable "Save changes" button ui->saveButton->setEnabled(false); //unselect item from flagList ui->flagList->clearSelection(); } void FlagManager::showFlag(int flagIdx) { if (flagIdx < 0 || flagIdx >= m_Model->rowCount()) { return; } else { ui->raBox->setText(m_Model->data(m_Model->index(flagIdx, 0)).toString()); ui->decBox->setText(m_Model->data(m_Model->index(flagIdx, 1)).toString()); ui->epochBox->setText(m_Model->data(m_Model->index(flagIdx, 2)).toString()); //ui->flagCombobox->setCurrentItem( m_Model->data( m_Model->index( flagIdx, 3) ).toString() ); ui->flagCombobox->setCurrentText(m_Model->data(m_Model->index(flagIdx, 3)).toString()); ui->flagLabel->setText(m_Model->data(m_Model->index(flagIdx, 4)).toString()); QColor labelColor = m_Model->item(flagIdx, 4)->foreground().color(); ui->labelColorcombo->setColor(labelColor); } ui->flagList->selectRow(flagIdx); ui->saveButton->setEnabled(true); } bool FlagManager::validatePoint() { bool raOk(false), decOk(false); dms ra(ui->raBox->createDms(false, &raOk)); //false means expressed in hours dms dec(ui->decBox->createDms(true, &decOk)); QString message; //check if ra & dec values were successfully converted if (!raOk || !decOk) { return false; } //make sure values are in valid range if (ra.Hours() < 0.0 || ra.Hours() > 24.0) message = i18n("The Right Ascension value must be between 0.0 and 24.0."); if (dec.Degrees() < -90.0 || dec.Degrees() > 90.0) message += '\n' + i18n("The Declination value must be between -90.0 and 90.0."); if (!message.isEmpty()) { - KMessageBox::sorry(0, message, i18n("Invalid Coordinate Data")); + KMessageBox::sorry(nullptr, message, i18n("Invalid Coordinate Data")); return false; } //all checks passed return true; } void FlagManager::deleteFlagItem(int flagIdx) { if (flagIdx < m_Model->rowCount()) { m_Model->removeRow(flagIdx); } } void FlagManager::slotAddFlag() { dms ra(ui->raBox->createDms(false)); //false means expressed in hours dms dec(ui->decBox->createDms(true)); insertFlag(true); FlagComponent *flags = m_Ks->data()->skyComposite()->flags(); //Add flag in FlagComponent SkyPoint flagPoint(ra, dec); flags->add(flagPoint, ui->epochBox->text(), ui->flagCombobox->currentText(), ui->flagLabel->text(), ui->labelColorcombo->color()); ui->flagList->selectRow(m_Model->rowCount() - 1); ui->saveButton->setEnabled(true); flags->saveToFile(); //Redraw map m_Ks->map()->forceUpdate(false); } void FlagManager::slotDeleteFlag() { int flag = ui->flagList->currentIndex().row(); //Remove from FlagComponent m_Ks->data()->skyComposite()->flags()->remove(flag); //Remove from list m_Model->removeRow(flag); //Clear form fields clearFields(); //Remove from file m_Ks->data()->skyComposite()->flags()->saveToFile(); //Redraw map m_Ks->map()->forceUpdate(false); } void FlagManager::slotCenterFlag() { if (ui->flagList->currentIndex().isValid()) { - m_Ks->map()->setClickedObject(0); + m_Ks->map()->setClickedObject(nullptr); m_Ks->map()->setClickedPoint( m_Ks->data()->skyComposite()->flags()->pointList().at(ui->flagList->currentIndex().row()).get()); m_Ks->map()->slotCenter(); } } void FlagManager::slotCenterTelescope() { #ifdef HAVE_INDI if (INDIListener::Instance()->size() == 0) { KMessageBox::sorry(0, i18n("KStars did not find any active telescopes.")); return; } foreach (ISD::GDInterface *gd, INDIListener::Instance()->getDevices()) { INDI::BaseDevice *bd = gd->getBaseDevice(); if (gd->getType() != KSTARS_TELESCOPE) continue; if (bd == nullptr) continue; if (bd->isConnected() == false) { KMessageBox::error(0, i18n("Telescope %1 is offline. Please connect and retry again.", gd->getDeviceName())); return; } ISD::GDSetCommand SlewCMD(INDI_SWITCH, "ON_COORD_SET", "TRACK", ISS_ON, this); gd->setProperty(&SlewCMD); gd->runCommand(INDI_SEND_COORDS, m_Ks->data()->skyComposite()->flags()->pointList().at(ui->flagList->currentIndex().row()).get()); return; } KMessageBox::sorry(0, i18n("KStars did not find any active telescopes.")); #endif } void FlagManager::slotSaveChanges() { int row = ui->flagList->currentIndex().row(); validatePoint(); insertFlag(false, row); m_Ks->map()->forceUpdate(); dms ra(ui->raBox->createDms(false)); //false means expressed in hours dms dec(ui->decBox->createDms(true)); SkyPoint flagPoint(ra, dec); //Update FlagComponent m_Ks->data()->skyComposite()->flags()->updateFlag(row, flagPoint, ui->epochBox->text(), ui->flagCombobox->currentText(), ui->flagLabel->text(), ui->labelColorcombo->color()); //Save changes to file m_Ks->data()->skyComposite()->flags()->saveToFile(); } void FlagManager::slotSetShownFlag(QModelIndex idx) { showFlag(idx.row()); } void FlagManager::insertFlag(bool isNew, int row) { dms ra(ui->raBox->createDms(false)); //false means expressed in hours dms dec(ui->decBox->createDms(true)); SkyPoint flagPoint(ra, dec); // Add flag in the list QList itemList; QStandardItem *labelItem = new QStandardItem(ui->flagLabel->text()); labelItem->setForeground(QBrush(ui->labelColorcombo->color())); FlagComponent *flags = m_Ks->data()->skyComposite()->flags(); QPixmap pixmap; itemList << new QStandardItem(flagPoint.ra0().toHMSString()) << new QStandardItem(flagPoint.dec0().toDMSString()) << new QStandardItem(ui->epochBox->text()) << new QStandardItem(QIcon(pixmap.fromImage(flags->imageList(ui->flagCombobox->currentIndex()))), ui->flagCombobox->currentText()) << labelItem; if (isNew) { m_Model->appendRow(itemList); } else { for (int i = 0; i < m_Model->columnCount(); i++) { m_Model->setItem(row, i, itemList.at(i)); } } } diff --git a/kstars/tools/modcalcangdist.cpp b/kstars/tools/modcalcangdist.cpp index fbcba6146..b7d978aeb 100644 --- a/kstars/tools/modcalcangdist.cpp +++ b/kstars/tools/modcalcangdist.cpp @@ -1,268 +1,268 @@ /*************************************************************************** modcalcapcoord.cpp - description ------------------- begin : Sun May 30 2004 copyright : (C) 2004 by Pablo de Vicente email : vicente@oan.es ***************************************************************************/ /*************************************************************************** * * * 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 "modcalcangdist.h" #include #include #include #include #include "dms.h" #include "widgets/dmsbox.h" #include "skyobjects/skypoint.h" #include "skyobjects/skyobject.h" #include "dialogs/finddialog.h" #include "kstars.h" modCalcAngDist::modCalcAngDist(QWidget *parentSplit) : QFrame(parentSplit) { setupUi(this); FirstRA->setDegType(false); SecondRA->setDegType(false); connect(FirstRA, SIGNAL(editingFinished()), this, SLOT(slotValidatePositions())); connect(FirstDec, SIGNAL(editingFinished()), this, SLOT(slotValidatePositions())); connect(SecondRA, SIGNAL(editingFinished()), this, SLOT(slotValidatePositions())); connect(SecondDec, SIGNAL(editingFinished()), this, SLOT(slotValidatePositions())); connect(FirstRA, SIGNAL(textEdited(QString)), this, SLOT(slotResetTitle())); connect(FirstDec, SIGNAL(textEdited(QString)), this, SLOT(slotResetTitle())); connect(SecondRA, SIGNAL(textEdited(QString)), this, SLOT(slotResetTitle())); connect(SecondDec, SIGNAL(textEdited(QString)), this, SLOT(slotResetTitle())); connect(FirstObjectButton, SIGNAL(clicked()), this, SLOT(slotObjectButton())); connect(SecondObjectButton, SIGNAL(clicked()), this, SLOT(slotObjectButton())); connect(runButtonBatch, SIGNAL(clicked()), this, SLOT(slotRunBatch())); show(); slotValidatePositions(); } modCalcAngDist::~modCalcAngDist() { } SkyPoint modCalcAngDist::getCoords(dmsBox *rBox, dmsBox *dBox, bool *ok) { dms raCoord, decCoord; bool ok2 = false; raCoord = rBox->createDms(false, &ok2); if (ok2) decCoord = dBox->createDms(true, &ok2); if (ok2) { if (ok) *ok = ok2; return SkyPoint(raCoord, decCoord); } else { if (ok) *ok = ok2; return SkyPoint(); } } void modCalcAngDist::slotValidatePositions() { SkyPoint sp0, sp1; bool ok; sp0 = getCoords(FirstRA, FirstDec, &ok); if (ok) sp1 = getCoords(SecondRA, SecondDec, &ok); if (ok) { double PA = 0; AngDist->setText(sp0.angularDistanceTo(&sp1, &PA).toDMSString()); PositionAngle->setText(QString::number(PA, 'f', 3)); } else { AngDist->setText(" .... "); PositionAngle->setText(" .... "); } } void modCalcAngDist::slotObjectButton() { QPointer fd = new FindDialog(this); if (fd->exec() == QDialog::Accepted) { SkyObject *o = fd->targetObject(); if (sender()->objectName() == QString("FirstObjectButton")) { FirstRA->showInHours(o->ra()); FirstDec->showInDegrees(o->dec()); FirstPositionBox->setTitle(i18n("First position: %1", o->name())); } else { SecondRA->showInHours(o->ra()); SecondDec->showInDegrees(o->dec()); SecondPositionBox->setTitle(i18n("Second position: %1", o->name())); } slotValidatePositions(); } delete fd; } void modCalcAngDist::slotResetTitle() { QString name = sender()->objectName(); if (name.contains("First")) FirstPositionBox->setTitle(i18n("First position")); else SecondPositionBox->setTitle(i18n("Second position")); } void modCalcAngDist::slotRunBatch() { QString inputFileName = InputLineEditBatch->url().toLocalFile(); // We open the input file and read its content if (QFile::exists(inputFileName)) { QFile f(inputFileName); if (!f.open(QIODevice::ReadOnly)) { QString message = i18n("Could not open file %1.", f.fileName()); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); inputFileName.clear(); return; } // processLines(&f); QTextStream istream(&f); processLines(istream); // readFile( istream ); f.close(); } else { QString message = i18n("Invalid file: %1", inputFileName); - KMessageBox::sorry(0, message, i18n("Invalid file")); + KMessageBox::sorry(nullptr, message, i18n("Invalid file")); inputFileName.clear(); InputLineEditBatch->setText(inputFileName); return; } } //void modCalcAngDist::processLines( const QFile * fIn ) { void modCalcAngDist::processLines(QTextStream &istream) { // we open the output file // QTextStream istream(&fIn); QString outputFileName; outputFileName = OutputLineEditBatch->text(); QFile fOut(outputFileName); fOut.open(QIODevice::WriteOnly); QTextStream ostream(&fOut); QString line; QChar space = ' '; int i = 0; SkyPoint sp0, sp1; double PA = 0; dms ra0B, dec0B, ra1B, dec1B, dist; while (!istream.atEnd()) { line = istream.readLine(); line.trimmed(); //Go through the line, looking for parameters QStringList fields = line.split(' '); i = 0; // Read RA and write in ostream if corresponds if (ra0CheckBatch->isChecked()) { ra0B = dms::fromString(fields[i], false); i++; } else ra0B = ra0BoxBatch->createDms(false); if (allRadioBatch->isChecked()) ostream << ra0B.toHMSString() << space; else if (ra0CheckBatch->isChecked()) ostream << ra0B.toHMSString() << space; // Read DEC and write in ostream if corresponds if (dec0CheckBatch->isChecked()) { dec0B = dms::fromString(fields[i], true); i++; } else dec0B = dec0BoxBatch->createDms(); if (allRadioBatch->isChecked()) ostream << dec0B.toDMSString() << space; else if (dec0CheckBatch->isChecked()) ostream << dec0B.toDMSString() << space; // Read RA and write in ostream if corresponds if (ra1CheckBatch->isChecked()) { ra1B = dms::fromString(fields[i], false); i++; } else ra1B = ra1BoxBatch->createDms(false); if (allRadioBatch->isChecked()) ostream << ra1B.toHMSString() << space; else if (ra1CheckBatch->isChecked()) ostream << ra1B.toHMSString() << space; // Read DEC and write in ostream if corresponds if (dec1CheckBatch->isChecked()) { dec1B = dms::fromString(fields[i], true); i++; } else dec1B = dec1BoxBatch->createDms(); if (allRadioBatch->isChecked()) ostream << dec1B.toDMSString() << space; else if (dec1CheckBatch->isChecked()) ostream << dec1B.toDMSString() << space; sp0 = SkyPoint(ra0B, dec0B); sp1 = SkyPoint(ra1B, dec1B); dist = sp0.angularDistanceTo(&sp1, &PA); ostream << dist.toDMSString() << QString::number(PA, 'f', 3) << endl; } fOut.close(); } diff --git a/kstars/tools/modcalcapcoord.cpp b/kstars/tools/modcalcapcoord.cpp index 56204c870..166dd0b14 100644 --- a/kstars/tools/modcalcapcoord.cpp +++ b/kstars/tools/modcalcapcoord.cpp @@ -1,297 +1,297 @@ /*************************************************************************** modcalcapcoord.cpp - description ------------------- begin : Wed Apr 10 2002 copyright : (C) 2002 by Pablo de Vicente email : vicente@oan.es ***************************************************************************/ /*************************************************************************** * * * 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 "modcalcapcoord.h" #include "dms.h" #include "kstars.h" #include "kstarsdatetime.h" #include "dialogs/finddialog.h" #include "skyobjects/skypoint.h" #include "skyobjects/skyobject.h" #include #include #include #include modCalcApCoord::modCalcApCoord(QWidget *parentSplit) : QFrame(parentSplit) { setupUi(this); showCurrentTime(); RACat->setDegType(false); DecCat->setDegType(true); connect(ObjectButton, SIGNAL(clicked()), this, SLOT(slotObject())); connect(NowButton, SIGNAL(clicked()), this, SLOT(showCurrentTime())); connect(RACat, SIGNAL(editingFinished()), this, SLOT(slotCompute())); connect(DecCat, SIGNAL(editingFinished()), this, SLOT(slotCompute())); connect(UT, SIGNAL(timeChanged(QTime)), this, SLOT(slotCompute())); connect(Date, SIGNAL(dateChanged(QDate)), this, SLOT(slotCompute())); connect(utCheckBatch, SIGNAL(clicked()), this, SLOT(slotUtCheckedBatch())); connect(dateCheckBatch, SIGNAL(clicked()), this, SLOT(slotDateCheckedBatch())); connect(raCheckBatch, SIGNAL(clicked()), this, SLOT(slotRaCheckedBatch())); connect(decCheckBatch, SIGNAL(clicked()), this, SLOT(slotDecCheckedBatch())); connect(epochCheckBatch, SIGNAL(clicked()), this, SLOT(slotEpochCheckedBatch())); connect(runButtonBatch, SIGNAL(clicked()), this, SLOT(slotRunBatch())); show(); } void modCalcApCoord::showCurrentTime(void) { KStarsDateTime dt(KStarsDateTime::currentDateTime()); Date->setDate(dt.date()); UT->setTime(dt.time()); EpochTarget->setText(QString::number(dt.epoch(), 'f', 3)); } void modCalcApCoord::slotCompute() { KStarsDateTime dt(Date->date(), UT->time()); long double jd = dt.djd(); dt.setFromEpoch(EpochCat->value()); long double jd0 = dt.djd(); SkyPoint sp(RACat->createDms(false), DecCat->createDms()); sp.apparentCoord(jd0, jd); RA->setText(sp.ra().toHMSString()); Dec->setText(sp.dec().toDMSString()); } void modCalcApCoord::slotObject() { QPointer fd = new FindDialog(this); if (fd->exec() == QDialog::Accepted) { SkyObject *o = fd->targetObject(); RACat->showInHours(o->ra0()); DecCat->showInDegrees(o->dec0()); EpochCat->setValue(2000.0); slotCompute(); } delete fd; } void modCalcApCoord::slotUtCheckedBatch() { if (utCheckBatch->isChecked()) utBoxBatch->setEnabled(false); else { utBoxBatch->setEnabled(true); } } void modCalcApCoord::slotDateCheckedBatch() { if (dateCheckBatch->isChecked()) dateBoxBatch->setEnabled(false); else { dateBoxBatch->setEnabled(true); } } void modCalcApCoord::slotRaCheckedBatch() { if (raCheckBatch->isChecked()) raBoxBatch->setEnabled(false); else { raBoxBatch->setEnabled(true); } } void modCalcApCoord::slotDecCheckedBatch() { if (decCheckBatch->isChecked()) decBoxBatch->setEnabled(false); else { decBoxBatch->setEnabled(true); } } void modCalcApCoord::slotEpochCheckedBatch() { if (epochCheckBatch->isChecked()) epochBoxBatch->setEnabled(false); else { epochBoxBatch->setEnabled(true); } } void modCalcApCoord::slotRunBatch() { QString inputFileName = InputLineEditBatch->url().toLocalFile(); // We open the input file and read its content if (QFile::exists(inputFileName)) { QFile f(inputFileName); if (!f.open(QIODevice::ReadOnly)) { QString message = i18n("Could not open file %1.", f.fileName()); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); inputFileName.clear(); return; } // processLines(&f); QTextStream istream(&f); processLines(istream); // readFile( istream ); f.close(); } else { QString message = i18n("Invalid file: %1", inputFileName); - KMessageBox::sorry(0, message, i18n("Invalid file")); + KMessageBox::sorry(nullptr, message, i18n("Invalid file")); inputFileName.clear(); InputLineEditBatch->setText(inputFileName); return; } } //void modCalcApCoord::processLines( const QFile * fIn ) { void modCalcApCoord::processLines(QTextStream &istream) { // we open the output file // QTextStream istream(&fIn); QString outputFileName; outputFileName = OutputLineEditBatch->text(); QFile fOut(outputFileName); fOut.open(QIODevice::WriteOnly); QTextStream ostream(&fOut); QString line; QChar space = ' '; int i = 0; long double jd, jd0; SkyPoint sp; QTime utB; QDate dtB; dms raB, decB; QString epoch0B; while (!istream.atEnd()) { line = istream.readLine(); line.trimmed(); //Go through the line, looking for parameters QStringList fields = line.split(' '); i = 0; // Read Ut and write in ostream if corresponds if (utCheckBatch->isChecked()) { utB = QTime::fromString(fields[i]); i++; } else utB = utBoxBatch->time(); if (allRadioBatch->isChecked()) ostream << QLocale().toString(utB) << space; else if (utCheckBatch->isChecked()) ostream << QLocale().toString(utB) << space; // Read date and write in ostream if corresponds if (dateCheckBatch->isChecked()) { dtB = QDate::fromString(fields[i]); i++; } else dtB = dateBoxBatch->date(); if (allRadioBatch->isChecked()) ostream << QLocale().toString(dtB, QLocale::LongFormat).append(space); else if (dateCheckBatch->isChecked()) ostream << QLocale().toString(dtB, QLocale::LongFormat).append(space); // Read RA and write in ostream if corresponds if (raCheckBatch->isChecked()) { raB = dms::fromString(fields[i], false); i++; } else raB = raBoxBatch->createDms(false); if (allRadioBatch->isChecked()) ostream << raB.toHMSString() << space; else if (raCheckBatch->isChecked()) ostream << raB.toHMSString() << space; // Read DEC and write in ostream if corresponds if (decCheckBatch->isChecked()) { decB = dms::fromString(fields[i], true); i++; } else decB = decBoxBatch->createDms(); if (allRadioBatch->isChecked()) ostream << decB.toDMSString() << space; else if (decCheckBatch->isChecked()) ostream << decB.toHMSString() << space; // Read Epoch and write in ostream if corresponds if (epochCheckBatch->isChecked()) { epoch0B = fields[i]; i++; } else epoch0B = epochBoxBatch->text(); if (allRadioBatch->isChecked()) ostream << epoch0B; else if (decCheckBatch->isChecked()) ostream << epoch0B; KStarsDateTime dt; dt.setFromEpoch(epoch0B); jd = KStarsDateTime(dtB, utB).djd(); jd0 = dt.djd(); sp = SkyPoint(raB, decB); sp.apparentCoord(jd0, jd); ostream << sp.ra().toHMSString() << sp.dec().toDMSString() << endl; } fOut.close(); } diff --git a/kstars/tools/modcalcdaylength.cpp b/kstars/tools/modcalcdaylength.cpp index b99d1f6ca..67a33842e 100644 --- a/kstars/tools/modcalcdaylength.cpp +++ b/kstars/tools/modcalcdaylength.cpp @@ -1,336 +1,336 @@ /*************************************************************************** modcalcdaylength.cpp - description ------------------- begin : wed jun 12 2002 copyright : (C) 2002 by Pablo de Vicente email : vicente@oan.es ***************************************************************************/ /*************************************************************************** * * * 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 "modcalcdaylength.h" #include "geolocation.h" #include "kstarsdata.h" #include "dialogs/locationdialog.h" #include "skyobjects/ksmoon.h" #include "skyobjects/kssun.h" #include "skyobjects/skyobject.h" #include modCalcDayLength::modCalcDayLength(QWidget *parentSplit) : QFrame(parentSplit) { setupUi(this); showCurrentDate(); initGeo(); slotComputeAlmanac(); connect(Date, SIGNAL(dateChanged(QDate)), this, SLOT(slotComputeAlmanac())); connect(Location, SIGNAL(clicked()), this, SLOT(slotLocation())); connect(LocationBatch, SIGNAL(clicked()), this, SLOT(slotLocationBatch())); connect(InputFileBatch, SIGNAL(urlSelected(QUrl)), this, SLOT(slotCheckFiles())); connect(OutputFileBatch, SIGNAL(urlSelected(QUrl)), this, SLOT(slotCheckFiles())); connect(RunButtonBatch, SIGNAL(clicked()), this, SLOT(slotRunBatch())); connect(ViewButtonBatch, SIGNAL(clicked()), this, SLOT(slotViewBatch())); RunButtonBatch->setEnabled(false); ViewButtonBatch->setEnabled(false); show(); } modCalcDayLength::~modCalcDayLength() { } void modCalcDayLength::showCurrentDate(void) { KStarsDateTime dt(KStarsDateTime::currentDateTime()); Date->setDate(dt.date()); } void modCalcDayLength::initGeo(void) { KStarsData *data = KStarsData::Instance(); geoPlace = data->geo(); geoBatch = data->geo(); Location->setText(geoPlace->fullName()); LocationBatch->setText(geoBatch->fullName()); } QTime modCalcDayLength::lengthOfDay(const QTime &setQTime, const QTime &riseQTime) { QTime dL(0, 0, 0); int dds = riseQTime.secsTo(setQTime); QTime dLength = dL.addSecs(dds); return dLength; } void modCalcDayLength::slotLocation() { QPointer ld = new LocationDialog(this); if (ld->exec() == QDialog::Accepted) { GeoLocation *newGeo = ld->selectedCity(); if (newGeo) { geoPlace = newGeo; Location->setText(geoPlace->fullName()); } } delete ld; slotComputeAlmanac(); } void modCalcDayLength::slotLocationBatch() { QPointer ld = new LocationDialog(this); if (ld->exec() == QDialog::Accepted) { GeoLocation *newGeo = ld->selectedCity(); if (newGeo) { geoBatch = newGeo; LocationBatch->setText(geoBatch->fullName()); } } delete ld; } void modCalcDayLength::updateAlmanac(const QDate &d, GeoLocation *geo) { //Determine values needed for the Almanac long double jd0 = KStarsDateTime(d, QTime(8, 0, 0)).djd(); KSNumbers num(jd0); //Sun KSSun Sun; Sun.findPosition(&num); QTime ssTime = Sun.riseSetTime(KStarsDateTime(jd0), geo, false); QTime srTime = Sun.riseSetTime(KStarsDateTime(jd0), geo, true); QTime stTime = Sun.transitTime(KStarsDateTime(jd0), geo); dms ssAz = Sun.riseSetTimeAz(KStarsDateTime(jd0), geo, false); dms srAz = Sun.riseSetTimeAz(KStarsDateTime(jd0), geo, true); dms stAlt = Sun.transitAltitude(KStarsDateTime(jd0), geo); //In most cases, the Sun will rise and set: if (ssTime.isValid()) { ssAzString = ssAz.toDMSString(); stAltString = stAlt.toDMSString(); srAzString = srAz.toDMSString(); ssTimeString = QLocale().toString(ssTime); srTimeString = QLocale().toString(srTime); stTimeString = QLocale().toString(stTime); QTime daylength = lengthOfDay(ssTime, srTime); //daylengthString = QLocale().toString(daylength); daylengthString = QLocale().toString(daylength, "hh:mm:ss"); //...but not always! } else if (stAlt.Degrees() > 0.) { ssAzString = i18n("Circumpolar"); stAltString = stAlt.toDMSString(); srAzString = i18n("Circumpolar"); ssTimeString = "--:--"; srTimeString = "--:--"; stTimeString = QLocale().toString(stTime); daylengthString = "24:00"; } else if (stAlt.Degrees() < 0.) { ssAzString = i18n("Does not rise"); stAltString = stAlt.toDMSString(); srAzString = i18n("Does not set"); ssTimeString = "--:--"; srTimeString = "--:--"; stTimeString = QLocale().toString(stTime); daylengthString = "00:00"; } //Moon KSMoon Moon; QTime msTime = Moon.riseSetTime(KStarsDateTime(jd0), geo, false); QTime mrTime = Moon.riseSetTime(KStarsDateTime(jd0), geo, true); QTime mtTime = Moon.transitTime(KStarsDateTime(jd0), geo); dms msAz = Moon.riseSetTimeAz(KStarsDateTime(jd0), geo, false); dms mrAz = Moon.riseSetTimeAz(KStarsDateTime(jd0), geo, true); dms mtAlt = Moon.transitAltitude(KStarsDateTime(jd0), geo); //In most cases, the Moon will rise and set: if (msTime.isValid()) { msAzString = msAz.toDMSString(); mtAltString = mtAlt.toDMSString(); mrAzString = mrAz.toDMSString(); msTimeString = QLocale().toString(msTime); mrTimeString = QLocale().toString(mrTime); mtTimeString = QLocale().toString(mtTime); //...but not always! } else if (mtAlt.Degrees() > 0.) { msAzString = i18n("Circumpolar"); mtAltString = mtAlt.toDMSString(); mrAzString = i18n("Circumpolar"); msTimeString = "--:--"; mrTimeString = "--:--"; mtTimeString = QLocale().toString(mtTime); } else if (mtAlt.Degrees() < 0.) { msAzString = i18n("Does not rise"); mtAltString = mtAlt.toDMSString(); mrAzString = i18n("Does not rise"); msTimeString = "--:--"; mrTimeString = "--:--"; mtTimeString = QLocale().toString(mtTime); } //after calling riseSetTime Phase needs to reset, setting it before causes Phase to set nan Moon.findPosition(&num); - Moon.findPhase(0); + Moon.findPhase(nullptr); lunarphaseString = Moon.phaseName() + " (" + QString::number(int(100 * Moon.illum())) + "%)"; //Fix length of Az strings if (srAz.Degrees() < 100.0) srAzString = ' ' + srAzString; if (ssAz.Degrees() < 100.0) ssAzString = ' ' + ssAzString; if (mrAz.Degrees() < 100.0) mrAzString = ' ' + mrAzString; if (msAz.Degrees() < 100.0) msAzString = ' ' + msAzString; } void modCalcDayLength::slotComputeAlmanac() { updateAlmanac(Date->date(), geoPlace); SunSet->setText(ssTimeString); SunRise->setText(srTimeString); SunTransit->setText(stTimeString); SunSetAz->setText(ssAzString); SunRiseAz->setText(srAzString); SunTransitAlt->setText(stAltString); DayLength->setText(daylengthString); MoonSet->setText(msTimeString); MoonRise->setText(mrTimeString); MoonTransit->setText(mtTimeString); MoonSetAz->setText(msAzString); MoonRiseAz->setText(mrAzString); MoonTransitAlt->setText(mtAltString); LunarPhase->setText(lunarphaseString); } void modCalcDayLength::slotCheckFiles() { bool flag = !InputFileBatch->lineEdit()->text().isEmpty() && !OutputFileBatch->lineEdit()->text().isEmpty(); RunButtonBatch->setEnabled(flag); } void modCalcDayLength::slotRunBatch() { QString inputFileName = InputFileBatch->url().toLocalFile(); if (QFile::exists(inputFileName)) { QFile f(inputFileName); if (!f.open(QIODevice::ReadOnly)) { QString message = i18n("Could not open file %1.", f.fileName()); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); return; } QTextStream istream(&f); processLines(istream); ViewButtonBatch->setEnabled(true); f.close(); } else { QString message = i18n("Invalid file: %1", inputFileName); - KMessageBox::sorry(0, message, i18n("Invalid file")); + KMessageBox::sorry(nullptr, message, i18n("Invalid file")); return; } } void modCalcDayLength::processLines(QTextStream &istream) { QFile fOut(OutputFileBatch->url().toLocalFile()); fOut.open(QIODevice::WriteOnly); QTextStream ostream(&fOut); //Write header ostream << "# " << i18nc("%1 is a location on earth", "Almanac for %1", geoBatch->fullName()) << QString(" [%1, %2]").arg(geoBatch->lng()->toDMSString(), geoBatch->lat()->toDMSString()) << endl << "# " << i18n("computed by KStars") << endl << "#" << endl << "# Date SRise STran SSet SRiseAz STranAlt SSetAz DayLen MRise MTran MSet " " MRiseAz MTranAlt MSetAz LunarPhase" << endl << "#" << endl; QString line; QDate d; while (!istream.atEnd()) { line = istream.readLine(); line = line.trimmed(); //Parse the line as a date, then compute Almanac values d = QDate::fromString(line); if (d.isValid()) { updateAlmanac(d, geoBatch); ostream << d.toString(Qt::ISODate) << " " << srTimeString << " " << stTimeString << " " << ssTimeString << " " << srAzString << " " << stAltString << " " << ssAzString << " " << daylengthString << " " << mrTimeString << " " << mtTimeString << " " << msTimeString << " " << mrAzString << " " << mtAltString << " " << msAzString << " " << lunarphaseString << endl; } } } void modCalcDayLength::slotViewBatch() { QFile fOut(OutputFileBatch->url().toLocalFile()); fOut.open(QIODevice::ReadOnly); QTextStream istream(&fOut); QStringList text; while (!istream.atEnd()) text.append(istream.readLine()); fOut.close(); - KMessageBox::informationList(0, i18n("Results of Almanac calculation"), text, OutputFileBatch->url().toLocalFile()); + KMessageBox::informationList(nullptr, i18n("Results of Almanac calculation"), text, OutputFileBatch->url().toLocalFile()); } diff --git a/kstars/tools/modcalcgalcoord.cpp b/kstars/tools/modcalcgalcoord.cpp index b25aae60d..98a69dd9a 100644 --- a/kstars/tools/modcalcgalcoord.cpp +++ b/kstars/tools/modcalcgalcoord.cpp @@ -1,350 +1,350 @@ /*************************************************************************** modcalcgal.cpp - description ------------------- begin : Thu Jan 17 2002 copyright : (C) 2002 by Pablo de Vicente email : vicente@oan.es ***************************************************************************/ /*************************************************************************** * * * 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 "modcalcgalcoord.h" #include "dialogs/finddialog.h" #include "skyobjects/skyobject.h" #include "skyobjects/skypoint.h" #include #include #include modCalcGalCoord::modCalcGalCoord(QWidget *parentSplit) : QFrame(parentSplit) { setupUi(this); RA->setDegType(false); connect(RA, SIGNAL(editingFinished()), this, SLOT(slotComputeCoords())); connect(Dec, SIGNAL(editingFinished()), this, SLOT(slotComputeCoords())); connect(GalLongitude, SIGNAL(editingFinished()), this, SLOT(slotComputeCoords())); connect(GalLatitude, SIGNAL(editingFinished()), this, SLOT(slotComputeCoords())); connect(ObjectButton, SIGNAL(clicked()), this, SLOT(slotObject())); connect(decCheckBatch, SIGNAL(clicked()), this, SLOT(slotDecCheckedBatch())); connect(raCheckBatch, SIGNAL(clicked()), this, SLOT(slotRaCheckedBatch())); connect(epochCheckBatch, SIGNAL(clicked()), this, SLOT(slotEpochCheckedBatch())); connect(galLongCheckBatch, SIGNAL(clicked()), this, SLOT(slotGalLongCheckedBatch())); connect(galLatCheckBatch, SIGNAL(clicked()), this, SLOT(slotGalLatCheckedBatch())); connect(runButtonBatch, SIGNAL(clicked()), this, SLOT(slotRunBatch())); show(); } modCalcGalCoord::~modCalcGalCoord() { } void modCalcGalCoord::slotObject() { QPointer fd = new FindDialog(this); if (fd->exec() == QDialog::Accepted) { SkyObject *o = fd->targetObject(); RA->showInHours(o->ra()); Dec->showInDegrees(o->dec()); slotComputeCoords(); } delete fd; } void modCalcGalCoord::slotComputeCoords() { if (GalLongitude->hasFocus()) GalLongitude->clearFocus(); //Determine whether we should compute galactic coords from //equatorial, or vice versa if (sender()->objectName() == "GalLongitude" || sender()->objectName() == "GalLatitude") { //Validate GLong and GLat bool ok(false); dms glat; dms glong = GalLongitude->createDms(true, &ok); if (ok) glat = GalLatitude->createDms(true, &ok); if (ok) { SkyPoint sp; sp.GalacticToEquatorial1950(&glong, &glat); sp.B1950ToJ2000(); RA->showInHours(sp.ra()); Dec->showInDegrees(sp.dec()); } } else { //Validate RA and Dec bool ok(false); dms dec; dms ra = RA->createDms(false, &ok); if (ok) dec = Dec->createDms(true, &ok); if (ok) { dms glong, glat; SkyPoint sp(ra, dec); sp.J2000ToB1950(); sp.Equatorial1950ToGalactic(glong, glat); GalLongitude->showInDegrees(glong); GalLatitude->showInDegrees(glat); } } } void modCalcGalCoord::galCheck() { galLatCheckBatch->setChecked(false); galLatBoxBatch->setEnabled(false); galLongCheckBatch->setChecked(false); galLongBoxBatch->setEnabled(false); galInputCoords = false; } void modCalcGalCoord::equCheck() { raCheckBatch->setChecked(false); raBoxBatch->setEnabled(false); decCheckBatch->setChecked(false); decBoxBatch->setEnabled(false); epochCheckBatch->setChecked(false); galInputCoords = true; } void modCalcGalCoord::slotRaCheckedBatch() { if (raCheckBatch->isChecked()) { raBoxBatch->setEnabled(false); galCheck(); } else { raBoxBatch->setEnabled(true); } } void modCalcGalCoord::slotDecCheckedBatch() { if (decCheckBatch->isChecked()) { decBoxBatch->setEnabled(false); galCheck(); } else { decBoxBatch->setEnabled(true); } } void modCalcGalCoord::slotEpochCheckedBatch() { epochCheckBatch->setChecked(false); if (epochCheckBatch->isChecked()) { epochBoxBatch->setEnabled(false); galCheck(); } else { epochBoxBatch->setEnabled(true); } } void modCalcGalCoord::slotGalLatCheckedBatch() { if (galLatCheckBatch->isChecked()) { galLatBoxBatch->setEnabled(false); equCheck(); } else { galLatBoxBatch->setEnabled(true); } } void modCalcGalCoord::slotGalLongCheckedBatch() { if (galLongCheckBatch->isChecked()) { galLongBoxBatch->setEnabled(false); equCheck(); } else { galLongBoxBatch->setEnabled(true); } } void modCalcGalCoord::slotRunBatch() { const QString inputFileName = InputFileBoxBatch->url().toLocalFile(); // We open the input file and read its content if (QFile::exists(inputFileName)) { QFile f(inputFileName); if (!f.open(QIODevice::ReadOnly)) { QString message = i18n("Could not open file %1.", f.fileName()); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); return; } // processLines(&f); QTextStream istream(&f); processLines(istream); // readFile( istream ); f.close(); } else { QString message = i18n("Invalid file: %1", inputFileName); - KMessageBox::sorry(0, message, i18n("Invalid file")); + KMessageBox::sorry(nullptr, message, i18n("Invalid file")); InputFileBoxBatch->setUrl(QUrl()); } } void modCalcGalCoord::processLines(QTextStream &istream) { // we open the output file // QTextStream istream(&fIn); const QString outputFileName = OutputFileBoxBatch->url().toLocalFile(); QFile fOut(outputFileName); fOut.open(QIODevice::WriteOnly); QTextStream ostream(&fOut); QString line; QChar space = ' '; int i = 0; SkyPoint sp; dms raB, decB, galLatB, galLongB; QString epoch0B; while (!istream.atEnd()) { line = istream.readLine(); line.trimmed(); //Go through the line, looking for parameters QStringList fields = line.split(' '); i = 0; // Input coords are galactic coordinates: if (galInputCoords) { // Read Galactic Longitude and write in ostream if corresponds if (galLongCheckBatch->isChecked()) { galLongB = dms::fromString(fields[i], true); i++; } else galLongB = galLongBoxBatch->createDms(true); if (allRadioBatch->isChecked()) ostream << galLongB.toDMSString() << space; else if (galLongCheckBatch->isChecked()) ostream << galLongB.toDMSString() << space; // Read Galactic Latitude and write in ostream if corresponds if (galLatCheckBatch->isChecked()) { galLatB = dms::fromString(fields[i], true); i++; } else galLatB = galLatBoxBatch->createDms(true); if (allRadioBatch->isChecked()) ostream << galLatB.toDMSString() << space; else if (galLatCheckBatch->isChecked()) ostream << galLatB.toDMSString() << space; sp = SkyPoint(); sp.GalacticToEquatorial1950(&galLongB, &galLatB); ostream << sp.ra().toHMSString() << space << sp.dec().toDMSString() << epoch0B << endl; // Input coords. are equatorial coordinates: } else { // Read RA and write in ostream if corresponds if (raCheckBatch->isChecked()) { raB = dms::fromString(fields[i], false); i++; } else raB = raBoxBatch->createDms(false); if (allRadioBatch->isChecked()) ostream << raB.toHMSString() << space; else if (raCheckBatch->isChecked()) ostream << raB.toHMSString() << space; // Read DEC and write in ostream if corresponds if (decCheckBatch->isChecked()) { decB = dms::fromString(fields[i], true); i++; } else decB = decBoxBatch->createDms(); if (allRadioBatch->isChecked()) ostream << decB.toDMSString() << space; else if (decCheckBatch->isChecked()) ostream << decB.toDMSString() << space; // Read Epoch and write in ostream if corresponds if (epochCheckBatch->isChecked()) { epoch0B = fields[i]; i++; } else epoch0B = epochBoxBatch->text(); if (allRadioBatch->isChecked()) ostream << epoch0B << space; else if (epochCheckBatch->isChecked()) ostream << epoch0B << space; sp = SkyPoint(raB, decB); sp.J2000ToB1950(); sp.Equatorial1950ToGalactic(galLongB, galLatB); ostream << galLongB.toDMSString() << space << galLatB.toDMSString() << endl; } } fOut.close(); } diff --git a/kstars/tools/modcalcgeodcoord.cpp b/kstars/tools/modcalcgeodcoord.cpp index 0b7d1189d..643691090 100644 --- a/kstars/tools/modcalcgeodcoord.cpp +++ b/kstars/tools/modcalcgeodcoord.cpp @@ -1,404 +1,404 @@ /*************************************************************************** modcalcgeodcoord.cpp - description ------------------- begin : Tue Jan 15 2002 copyright : (C) 2002 by Pablo de Vicente email : vicente@oan.es ***************************************************************************/ /*************************************************************************** * * * 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 "modcalcgeodcoord.h" #include "dms.h" #include "geolocation.h" #include "kstars.h" #include "kstarsdata.h" #include #include #include modCalcGeodCoord::modCalcGeodCoord(QWidget *parentSplit) : QFrame(parentSplit) { QStringList ellipsoidList; ellipsoidList << "IAU76" << "GRS80" << "MERIT83" << "WGS84" << "IERS89"; setupUi(this); spheRadio->setChecked(true); ellipsoidBox->insertItems(5, ellipsoidList); geoPlace.reset(new GeoLocation(dms(0), dms(0))); showLongLat(); setEllipsoid(0); show(); connect(Clear, SIGNAL(clicked()), this, SLOT(slotClearGeoCoords())); connect(Compute, SIGNAL(clicked()), this, SLOT(slotComputeGeoCoords())); } void modCalcGeodCoord::showLongLat(void) { KStarsData *data = KStarsData::Instance(); LongGeoBox->show(data->geo()->lng()); LatGeoBox->show(data->geo()->lat()); AltGeoBox->setText(QString("0.0")); } void modCalcGeodCoord::setEllipsoid(int index) { geoPlace->changeEllipsoid(index); } void modCalcGeodCoord::getCartGeoCoords(void) { geoPlace->setXPos(XGeoBox->text().toDouble() * 1000.); geoPlace->setYPos(YGeoBox->text().toDouble() * 1000.); geoPlace->setZPos(ZGeoBox->text().toDouble() * 1000.); } void modCalcGeodCoord::getSphGeoCoords(void) { geoPlace->setLong(LongGeoBox->createDms()); geoPlace->setLat(LatGeoBox->createDms()); geoPlace->setHeight(AltGeoBox->text().toDouble()); } void modCalcGeodCoord::slotClearGeoCoords(void) { geoPlace->setLong(dms(0.0)); geoPlace->setLat(dms(0.0)); geoPlace->setHeight(0.0); LatGeoBox->clearFields(); LongGeoBox->clearFields(); } void modCalcGeodCoord::slotComputeGeoCoords(void) { if (cartRadio->isChecked()) { getCartGeoCoords(); showSpheGeoCoords(); } else { getSphGeoCoords(); showCartGeoCoords(); } } void modCalcGeodCoord::showSpheGeoCoords(void) { LongGeoBox->show(geoPlace->lng()); LatGeoBox->show(geoPlace->lat()); AltGeoBox->setText(QLocale().toString(geoPlace->height(), 3)); } void modCalcGeodCoord::showCartGeoCoords(void) { XGeoBox->setText(QLocale().toString(geoPlace->xPos() / 1000., 6)); YGeoBox->setText(QLocale().toString(geoPlace->yPos() / 1000., 6)); ZGeoBox->setText(QLocale().toString(geoPlace->zPos() / 1000., 6)); } void modCalcGeodCoord::geoCheck(void) { XGeoBoxBatch->setEnabled(false); XGeoCheckBatch->setChecked(false); YGeoBoxBatch->setEnabled(false); YGeoCheckBatch->setChecked(false); YGeoBoxBatch->setEnabled(false); YGeoCheckBatch->setChecked(false); xyzInputCoords = false; } void modCalcGeodCoord::xyzCheck(void) { LongGeoBoxBatch->setEnabled(false); LongGeoCheckBatch->setChecked(false); LatGeoBoxBatch->setEnabled(false); LatGeoCheckBatch->setChecked(false); AltGeoBoxBatch->setEnabled(false); AltGeoCheckBatch->setChecked(false); xyzInputCoords = true; } void modCalcGeodCoord::slotLongCheckedBatch() { if (LongGeoCheckBatch->isChecked()) { LongGeoBoxBatch->setEnabled(false); geoCheck(); } else { LongGeoBoxBatch->setEnabled(true); } } void modCalcGeodCoord::slotLatCheckedBatch() { if (LatGeoCheckBatch->isChecked()) { LatGeoBoxBatch->setEnabled(false); geoCheck(); } else { LatGeoBoxBatch->setEnabled(true); } } void modCalcGeodCoord::slotElevCheckedBatch() { if (AltGeoCheckBatch->isChecked()) { AltGeoBoxBatch->setEnabled(false); geoCheck(); } else { AltGeoBoxBatch->setEnabled(true); } } void modCalcGeodCoord::slotXCheckedBatch() { if (XGeoCheckBatch->isChecked()) { XGeoBoxBatch->setEnabled(false); xyzCheck(); } else { XGeoBoxBatch->setEnabled(true); } } void modCalcGeodCoord::slotYCheckedBatch() { if (YGeoCheckBatch->isChecked()) { YGeoBoxBatch->setEnabled(false); xyzCheck(); } else { YGeoBoxBatch->setEnabled(true); } } void modCalcGeodCoord::slotZCheckedBatch() { if (ZGeoCheckBatch->isChecked()) { ZGeoBoxBatch->setEnabled(false); xyzCheck(); } else { ZGeoBoxBatch->setEnabled(true); } } void modCalcGeodCoord::slotInputFile() { const QString inputFileName = QFileDialog::getOpenFileName(KStars::Instance(), QString(), QString()); if (!inputFileName.isEmpty()) InputFileBoxBatch->setUrl(QUrl::fromLocalFile(inputFileName)); } void modCalcGeodCoord::slotOutputFile() { const QString outputFileName = QFileDialog::getSaveFileName(); if (!outputFileName.isEmpty()) OutputFileBoxBatch->setUrl(QUrl::fromLocalFile(outputFileName)); } void modCalcGeodCoord::slotRunBatch(void) { const QString inputFileName = InputFileBoxBatch->url().toLocalFile(); // We open the input file and read its content if (QFile::exists(inputFileName)) { QFile f(inputFileName); if (!f.open(QIODevice::ReadOnly)) { QString message = i18n("Could not open file %1.", f.fileName()); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); return; } // processLines(&f); QTextStream istream(&f); processLines(istream); // readFile( istream ); f.close(); } else { QString message = i18n("Invalid file: %1", inputFileName); - KMessageBox::sorry(0, message, i18n("Invalid file")); + KMessageBox::sorry(nullptr, message, i18n("Invalid file")); InputFileBoxBatch->setUrl(QUrl()); } } void modCalcGeodCoord::processLines(QTextStream &istream) { // we open the output file // QTextStream istream(&fIn); const QString outputFileName = OutputFileBoxBatch->url().toLocalFile(); QFile fOut(outputFileName); fOut.open(QIODevice::WriteOnly); QTextStream ostream(&fOut); QString line; QChar space = ' '; int i = 0; GeoLocation geoPl(dms(0), dms(0)); geoPl.setEllipsoid(0); double xB, yB, zB, hB; dms latB, longB; while (!istream.atEnd()) { line = istream.readLine(); line.trimmed(); //Go through the line, looking for parameters QStringList fields = line.split(' '); i = 0; // Input coords are XYZ: if (xyzInputCoords) { // Read X and write in ostream if corresponds if (XGeoCheckBatch->isChecked()) { xB = fields[i].toDouble(); i++; } else xB = XGeoBoxBatch->text().toDouble(); if (AllRadioBatch->isChecked()) ostream << xB << space; else if (XGeoCheckBatch->isChecked()) ostream << xB << space; // Read Y and write in ostream if corresponds if (YGeoCheckBatch->isChecked()) { yB = fields[i].toDouble(); i++; } else yB = YGeoBoxBatch->text().toDouble(); if (AllRadioBatch->isChecked()) ostream << yB << space; else if (YGeoCheckBatch->isChecked()) ostream << yB << space; // Read Z and write in ostream if corresponds if (ZGeoCheckBatch->isChecked()) { zB = fields[i].toDouble(); i++; } else zB = ZGeoBoxBatch->text().toDouble(); if (AllRadioBatch->isChecked()) ostream << zB << space; else if (YGeoCheckBatch->isChecked()) ostream << zB << space; geoPl.setXPos(xB * 1000.0); geoPl.setYPos(yB * 1000.0); geoPl.setZPos(zB * 1000.0); ostream << geoPl.lng()->toDMSString() << space << geoPl.lat()->toDMSString() << space << geoPl.height() << endl; // Input coords. are Long, Lat and Height } else { // Read Longitude and write in ostream if corresponds if (LongGeoCheckBatch->isChecked()) { longB = dms::fromString(fields[i], true); i++; } else longB = LongGeoBoxBatch->createDms(true); if (AllRadioBatch->isChecked()) ostream << longB.toDMSString() << space; else if (LongGeoCheckBatch->isChecked()) ostream << longB.toDMSString() << space; // Read Latitude and write in ostream if corresponds if (LatGeoCheckBatch->isChecked()) { latB = dms::fromString(fields[i], true); i++; } else latB = LatGeoBoxBatch->createDms(true); if (AllRadioBatch->isChecked()) ostream << latB.toDMSString() << space; else if (LatGeoCheckBatch->isChecked()) ostream << latB.toDMSString() << space; // Read Height and write in ostream if corresponds if (AltGeoCheckBatch->isChecked()) { hB = fields[i].toDouble(); i++; } else hB = AltGeoBoxBatch->text().toDouble(); if (AllRadioBatch->isChecked()) ostream << hB << space; else if (AltGeoCheckBatch->isChecked()) ostream << hB << space; geoPl.setLong(longB); geoPl.setLat(latB); geoPl.setHeight(hB); ostream << geoPl.xPos() / 1000.0 << space << geoPl.yPos() / 1000.0 << space << geoPl.zPos() / 1000.0 << endl; } } fOut.close(); } diff --git a/kstars/tools/modcalcjd.cpp b/kstars/tools/modcalcjd.cpp index 299094009..7e8f640e7 100644 --- a/kstars/tools/modcalcjd.cpp +++ b/kstars/tools/modcalcjd.cpp @@ -1,257 +1,257 @@ /*************************************************************************** modcalcjd.cpp - description ------------------- begin : Tue Jan 15 2002 copyright : (C) 2002 by Pablo de Vicente email : vicente@oan.es ***************************************************************************/ /*************************************************************************** * * * 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 "modcalcjd.h" #include #include #include #include #include #include #include "kstarsdatetime.h" #define MJD0 2400000.5 modCalcJD::modCalcJD(QWidget *parentSplit) : QFrame(parentSplit) { setupUi(this); // signals and slots connections connect(NowButton, SIGNAL(clicked()), this, SLOT(showCurrentTime())); connect(DateTimeBox, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(slotUpdateCalendar())); connect(JDBox, SIGNAL(editingFinished()), this, SLOT(slotUpdateJD())); connect(ModJDBox, SIGNAL(editingFinished()), this, SLOT(slotUpdateModJD())); connect(InputFileBatch, SIGNAL(urlSelected(QUrl)), this, SLOT(slotCheckFiles())); connect(OutputFileBatch, SIGNAL(urlSelected(QUrl)), this, SLOT(slotCheckFiles())); connect(RunButtonBatch, SIGNAL(clicked()), this, SLOT(slotRunBatch())); connect(ViewButtonBatch, SIGNAL(clicked()), this, SLOT(slotViewBatch())); RunButtonBatch->setEnabled(false); ViewButtonBatch->setEnabled(false); showCurrentTime(); slotUpdateCalendar(); show(); } modCalcJD::~modCalcJD(void) { } void modCalcJD::slotUpdateCalendar() { long double julianDay, modjulianDay; julianDay = KStarsDateTime(DateTimeBox->dateTime()).djd(); showJd(julianDay); modjulianDay = julianDay - MJD0; showMjd(modjulianDay); } void modCalcJD::slotUpdateModJD() { long double julianDay, modjulianDay; modjulianDay = ModJDBox->text().toDouble(); julianDay = MJD0 + modjulianDay; showJd(julianDay); DateTimeBox->setDateTime(KStarsDateTime(julianDay)); } void modCalcJD::slotUpdateJD() { long double julianDay, modjulianDay; julianDay = JDBox->text().toDouble(); KStarsDateTime dt(julianDay); DateTimeBox->setDateTime(dt); modjulianDay = julianDay - MJD0; showMjd(modjulianDay); } void modCalcJD::showCurrentTime(void) { DateTimeBox->setDateTime(KStarsDateTime::currentDateTime()); } void modCalcJD::showJd(long double julianDay) { JDBox->setText(QLocale().toString((double)julianDay, 5)); } void modCalcJD::showMjd(long double modjulianDay) { ModJDBox->setText(QLocale().toString((double)modjulianDay, 5)); } void modCalcJD::slotCheckFiles() { if (!InputFileBatch->lineEdit()->text().isEmpty() && !OutputFileBatch->lineEdit()->text().isEmpty()) { RunButtonBatch->setEnabled(true); } else { RunButtonBatch->setEnabled(false); } } void modCalcJD::slotRunBatch() { QString inputFileName = InputFileBatch->url().toLocalFile(); if (QFile::exists(inputFileName)) { QFile f(inputFileName); if (!f.open(QIODevice::ReadOnly)) { QString message = i18n("Could not open file %1.", f.fileName()); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); return; } QTextStream istream(&f); processLines(istream, InputComboBatch->currentIndex()); ViewButtonBatch->setEnabled(true); f.close(); } else { QString message = i18n("Invalid file: %1", inputFileName); - KMessageBox::sorry(0, message, i18n("Invalid file")); + KMessageBox::sorry(nullptr, message, i18n("Invalid file")); return; } } void modCalcJD::processLines(QTextStream &istream, int inputData) { QFile fOut(OutputFileBatch->url().toLocalFile()); fOut.open(QIODevice::WriteOnly); QTextStream ostream(&fOut); QString line; long double jd(0); double mjd(0); KStarsDateTime dt; while (!istream.atEnd()) { line = istream.readLine(); line = line.trimmed(); QStringList data = line.split(' ', QString::SkipEmptyParts); if (inputData == 0) //Parse date & time { //Is the first field parseable as a date or date&time? if (data[0].length() > 10) dt = KStarsDateTime::fromString(data[0]); else dt = KStarsDateTime(QDate::fromString(data[0]), QTime(0, 0, 0)); //DEBUG qDebug() << data[0]; if (dt.isValid()) qDebug() << dt.toString(); if (dt.isValid()) { //Try to parse the second field as a time if (data.size() > 1) { QString s = data[1]; if (s.length() == 4) s = '0' + s; QTime t = QTime::fromString(s); if (t.isValid()) dt.setTime(t); } } else //Did not parse the first field as a date; try it as a time { QTime t = QTime::fromString(data[0]); if (t.isValid()) { dt.setTime(t); //Now try the second field as a date if (data.size() > 1) { QDate d = QDate::fromString(data[1]); if (d.isValid()) dt.setDate(d); else dt.setDate(QDate::currentDate()); } } } if (dt.isValid()) { //Compute JD and MJD jd = dt.djd(); mjd = jd - MJD0; } } else if (inputData == 1) //Parse Julian day { bool ok(false); jd = data[0].toDouble(&ok); if (ok) { dt.setDJD(jd); mjd = jd - MJD0; } } else if (inputData == 2) //Parse Modified Julian day { bool ok(false); mjd = data[0].toDouble(&ok); if (ok) { jd = mjd + MJD0; dt.setDJD(jd); } } //Write to output file ostream << QLocale().toString(dt, QLocale::LongFormat) << " " << QString::number(jd, 'f', 2) << " " << QString::number(mjd, 'f', 2) << endl; } fOut.close(); } void modCalcJD::slotViewBatch() { QFile fOut(OutputFileBatch->url().toLocalFile()); fOut.open(QIODevice::ReadOnly); QTextStream istream(&fOut); QStringList text; while (!istream.atEnd()) text.append(istream.readLine()); fOut.close(); - KMessageBox::informationList(0, i18n("Results of Julian day calculation"), text, + KMessageBox::informationList(nullptr, i18n("Results of Julian day calculation"), text, OutputFileBatch->url().toLocalFile()); } diff --git a/kstars/tools/modcalcplanets.cpp b/kstars/tools/modcalcplanets.cpp index b1543227a..a5d8017dd 100644 --- a/kstars/tools/modcalcplanets.cpp +++ b/kstars/tools/modcalcplanets.cpp @@ -1,457 +1,457 @@ /*************************************************************************** modcalcequinox.cpp - description ------------------- begin : dom may 2 2004 copyright : (C) 2004-2005 by Pablo de Vicente email : p.devicentea@wanadoo.es ***************************************************************************/ /*************************************************************************** * * * 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 "modcalcplanets.h" #include "geolocation.h" #include "kstarsdata.h" #include "dialogs/locationdialog.h" #include "skyobjects/ksmoon.h" #include "skyobjects/kssun.h" modCalcPlanets::modCalcPlanets(QWidget *parentSplit) : QFrame(parentSplit) { setupUi(this); KStarsDateTime dt(KStarsDateTime::currentDateTime()); DateTimeBox->setDateTime(dt); DateBoxBatch->setDate(dt.date()); UTBoxBatch->setTime(dt.time()); geoPlace = KStarsData::Instance()->geo(); LocationButton->setText(geoPlace->fullName()); RABox->setDegType(false); // signals and slots connections connect(PlanetComboBox, SIGNAL(activated(int)), this, SLOT(slotComputePosition())); connect(DateTimeBox, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(slotComputePosition())); connect(LocationButton, SIGNAL(clicked()), this, SLOT(slotLocation())); connect(UTCheckBatch, SIGNAL(clicked()), this, SLOT(slotUtCheckedBatch())); connect(DateCheckBatch, SIGNAL(clicked()), this, SLOT(slotDateCheckedBatch())); connect(LatCheckBatch, SIGNAL(clicked()), this, SLOT(slotLatCheckedBatch())); connect(LongCheckBatch, SIGNAL(clicked()), this, SLOT(slotLongCheckedBatch())); connect(PlanetCheckBatch, SIGNAL(clicked()), this, SLOT(slotPlanetsCheckedBatch())); slotComputePosition(); show(); } modCalcPlanets::~modCalcPlanets() { } void modCalcPlanets::slotLocation() { QPointer ld = new LocationDialog(this); if (ld->exec() == QDialog::Accepted) { geoPlace = ld->selectedCity(); LocationButton->setText(geoPlace->fullName()); slotComputePosition(); } delete ld; } void modCalcPlanets::slotComputePosition() { KStarsDateTime dt(DateTimeBox->dateTime()); long double julianDay = dt.djd(); KSNumbers num(julianDay); CachingDms LST(geoPlace->GSTtoLST(dt.gst())); // Earth KSPlanet Earth(I18N_NOOP("Earth")); Earth.findPosition(&num); // Earth is special case! if (PlanetComboBox->currentIndex() == 2) { showCoordinates(Earth); return; } // Pointer to hold planet data. Pointer is used since it has to // hold objects of different type. It's safe to use new/delete // because exceptions are disallowed. std::unique_ptr p; switch (PlanetComboBox->currentIndex()) { case 0: p.reset(new KSPlanet(KSPlanetBase::MERCURY)); break; case 1: p.reset(new KSPlanet(KSPlanetBase::VENUS)); break; case 3: p.reset(new KSPlanet(KSPlanetBase::MARS)); break; case 4: p.reset(new KSPlanet(KSPlanetBase::JUPITER)); break; case 5: p.reset(new KSPlanet(KSPlanetBase::SATURN)); break; case 6: p.reset(new KSPlanet(KSPlanetBase::URANUS)); break; case 7: p.reset(new KSPlanet(KSPlanetBase::NEPTUNE)); break; /*case 8: p.reset(new KSPluto(); break;*/ case 8: p.reset(new KSMoon()); break; case 9: p.reset(new KSSun()); p->setRsun(0.0); break; } if (p.get() == nullptr) return; // Show data. p->findPosition(&num, geoPlace->lat(), &LST, &Earth); p->EquatorialToHorizontal(&LST, geoPlace->lat()); showCoordinates(*p); } void modCalcPlanets::showCoordinates(const KSPlanetBase &ksp) { showHeliocentricEclipticCoords(ksp.helEcLong(), ksp.helEcLat(), ksp.rsun()); showGeocentricEclipticCoords(ksp.ecLong(), ksp.ecLat(), ksp.rearth()); showEquatorialCoords(ksp.ra(), ksp.dec()); showTopocentricCoords(ksp.az(), ksp.alt()); } void modCalcPlanets::showHeliocentricEclipticCoords(const dms &hLong, const dms &hLat, double dist) { HelioLongBox->show(hLong); HelioLatBox->show(hLat); HelioDistBox->setText(QLocale().toString(dist, 6)); } void modCalcPlanets::showGeocentricEclipticCoords(const dms &eLong, const dms &eLat, double dist) { GeoLongBox->show(eLong); GeoLatBox->show(eLat); GeoDistBox->setText(QLocale().toString(dist, 6)); } void modCalcPlanets::showEquatorialCoords(const dms &ra, const dms &dec) { RABox->show(ra, false); DecBox->show(dec); } void modCalcPlanets::showTopocentricCoords(const dms &az, const dms &el) { AzBox->show(az); AltBox->show(el); } void modCalcPlanets::slotPlanetsCheckedBatch() { PlanetComboBoxBatch->setEnabled(!PlanetCheckBatch->isChecked()); } void modCalcPlanets::slotUtCheckedBatch() { UTBoxBatch->setEnabled(!UTCheckBatch->isChecked()); } void modCalcPlanets::slotDateCheckedBatch() { DateBoxBatch->setEnabled(!DateCheckBatch->isChecked()); } void modCalcPlanets::slotLongCheckedBatch() { LongBoxBatch->setEnabled(!LongCheckBatch->isChecked()); } void modCalcPlanets::slotLatCheckedBatch() { LatBoxBatch->setEnabled(!LatCheckBatch->isChecked()); } void modCalcPlanets::slotRunBatch() { const QString inputFileName = InputFileBoxBatch->url().toLocalFile(); // We open the input file and read its content if (QFile::exists(inputFileName)) { QFile f(inputFileName); if (!f.open(QIODevice::ReadOnly)) { QString message = i18n("Could not open file %1.", f.fileName()); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); return; } QTextStream istream(&f); processLines(istream); f.close(); } else { QString message = i18n("Invalid file: %1", inputFileName); - KMessageBox::sorry(0, message, i18n("Invalid file")); + KMessageBox::sorry(nullptr, message, i18n("Invalid file")); InputFileBoxBatch->setUrl(QUrl()); } } unsigned int modCalcPlanets::requiredBatchFields() { unsigned int i = 0; if (PlanetCheckBatch->isChecked()) i++; if (UTCheckBatch->isChecked()) i++; if (DateCheckBatch->isChecked()) i++; if (LongCheckBatch->isChecked()) i++; if (LatCheckBatch->isChecked()) i++; return i; } void modCalcPlanets::processLines(QTextStream &istream) { // we open the output file const QString outputFileName = OutputFileBoxBatch->url().toLocalFile(); QFile fOut(outputFileName); fOut.open(QIODevice::WriteOnly); QTextStream ostream(&fOut); bool lineIsValid = true; QChar space = ' '; QString planetB; unsigned int i = 0, nline = 0; QTime utB; QDate dtB; CachingDms longB, latB, hlongB, hlatB, glongB, glatB, raB, decB, azmB, altB; double rSunB(0.0), rEarthB(0.0); //Initialize planet names QString pn; QStringList pNames, pNamesi18n; pNames << "Mercury" << "Venus" << "Earth" << "Mars" << "Jupiter" << "Saturn" << "Uranus" << "Neptune" /* << "Pluto" */ << "Sun" << "Moon"; pNamesi18n << i18n("Mercury") << i18n("Venus") << i18n("Earth") << i18n("Mars") << i18n("Jupiter") << i18n("Saturn") << i18n("Uranus") << i18n("Neptune") /* << i18n("Pluto") */ << i18n("Sun") << i18n("Moon"); ///Parse the input file int numberOfRequiredFields = requiredBatchFields(); while (!istream.atEnd()) { QString lineToWrite; QString line = istream.readLine(); line.trimmed(); //Go through the line, looking for parameters QStringList fields = line.split(' '); if (fields.count() != numberOfRequiredFields) { lineIsValid = false; qWarning() << i18n("Incorrect number of fields in line %1: ", nline) << i18n("Present fields %1. ", fields.count()) << i18n("Required fields %1. ", numberOfRequiredFields) << endl; nline++; continue; } i = 0; if (PlanetCheckBatch->isChecked()) { planetB = fields[i]; int j = pNamesi18n.indexOf(planetB); if (j == -1) { qWarning() << i18n("Unknown planet ") << fields[i] << i18n(" in line %1: ", nline) << endl; continue; } pn = pNames.at(j); //untranslated planet name i++; } else { planetB = PlanetComboBoxBatch->currentText(); } if (AllRadioBatch->isChecked() || PlanetCheckBatch->isChecked()) { lineToWrite = planetB; lineToWrite += space; } // Read Ut and write in ostream if corresponds if (UTCheckBatch->isChecked()) { utB = QTime::fromString(fields[i]); if (!utB.isValid()) { qWarning() << i18n("Line %1 contains an invalid time", nline); lineIsValid = false; nline++; continue; } i++; } else { utB = UTBoxBatch->time(); } if (AllRadioBatch->isChecked() || UTCheckBatch->isChecked()) lineToWrite += QLocale().toString(utB).append(space); // Read date and write in ostream if corresponds if (DateCheckBatch->isChecked()) { dtB = QDate::fromString(fields[i], Qt::ISODate); if (!dtB.isValid()) { qWarning() << i18n("Line %1 contains an invalid date: ", nline) << fields[i] << endl; lineIsValid = false; nline++; continue; } i++; } else { dtB = DateBoxBatch->date(); } if (AllRadioBatch->isChecked() || DateCheckBatch->isChecked()) lineToWrite += QLocale().toString(dtB, QLocale::LongFormat).append(space); // Read Longitude and write in ostream if corresponds if (LongCheckBatch->isChecked()) { longB = CachingDms::fromString(fields[i], true); i++; } else { longB = LongBoxBatch->createDms(true); } if (AllRadioBatch->isChecked() || LongCheckBatch->isChecked()) lineToWrite += longB.toDMSString() + space; // Read Latitude if (LatCheckBatch->isChecked()) { latB = CachingDms::fromString(fields[i], true); i++; } else { latB = LatBoxBatch->createDms(true); } if (AllRadioBatch->isChecked() || LatCheckBatch->isChecked()) lineToWrite += latB.toDMSString() + space; KStarsDateTime edt(dtB, utB); CachingDms LST = edt.gst() + longB; KSNumbers num(edt.djd()); KSPlanet Earth(I18N_NOOP("Earth")); Earth.findPosition(&num); // FIXME: allocate new object for every iteration is probably not wisest idea. - KSPlanetBase *kspb = 0; + KSPlanetBase *kspb = nullptr; /*if ( pn == "Pluto" ) { kspb = new KSPluto(); } else*/ if (pn == "Sun") { kspb = new KSSun(); } else if (pn == "Moon") { kspb = new KSMoon(); } else { kspb = new KSPlanet(i18n(pn.toLocal8Bit()), QString(), Qt::white, 1.0); } kspb->findPosition(&num, &latB, &LST, &Earth); kspb->EquatorialToHorizontal(&LST, &latB); // Heliocentric Ecl. coords. hlongB = kspb->helEcLong(); hlatB = kspb->helEcLat(); rSunB = kspb->rsun(); // Geocentric Ecl. coords. glongB = kspb->ecLong(); glatB = kspb->ecLat(); rEarthB = kspb->rearth(); // Equatorial coords. decB = kspb->dec(); raB = kspb->ra(); // Topocentric Coords. azmB = kspb->az(); altB = kspb->alt(); ostream << lineToWrite; if (HelioEclCheckBatch->isChecked()) ostream << hlongB.toDMSString() << space << hlatB.toDMSString() << space << rSunB << space; if (GeoEclCheckBatch->isChecked()) ostream << glongB.toDMSString() << space << glatB.toDMSString() << space << rEarthB << space; if (EquatorialCheckBatch->isChecked()) ostream << raB.toHMSString() << space << decB.toDMSString() << space; if (HorizontalCheckBatch->isChecked()) ostream << azmB.toDMSString() << space << altB.toDMSString() << space; ostream << endl; // Delete object delete kspb; nline++; } if (!lineIsValid) { QString message = i18n("Errors found while parsing some lines in the input file"); - KMessageBox::sorry(0, message, i18n("Errors in lines")); + KMessageBox::sorry(nullptr, message, i18n("Errors in lines")); } fOut.close(); } diff --git a/kstars/tools/modcalcsidtime.cpp b/kstars/tools/modcalcsidtime.cpp index b951bb069..89e1c0358 100644 --- a/kstars/tools/modcalcsidtime.cpp +++ b/kstars/tools/modcalcsidtime.cpp @@ -1,372 +1,372 @@ /*************************************************************************** modcalcsidtime.cpp - description ------------------- begin : Wed Jan 23 2002 copyright : (C) 2002 by Pablo de Vicente email : vicente@oan.es ***************************************************************************/ /*************************************************************************** * * * 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 "modcalcsidtime.h" #include "kstarsdata.h" #include "kstarsdatetime.h" #include "dialogs/locationdialog.h" #include #include #include modCalcSidTime::modCalcSidTime(QWidget *parent) : QFrame(parent) { setupUi(this); //Preset date and location showCurrentTimeAndLocation(); // signals and slots connections connect(LocationButton, SIGNAL(clicked()), this, SLOT(slotChangeLocation())); connect(Date, SIGNAL(dateChanged(QDate)), this, SLOT(slotChangeDate())); connect(LT, SIGNAL(timeChanged(QTime)), this, SLOT(slotConvertST(QTime))); connect(ST, SIGNAL(timeChanged(QTime)), this, SLOT(slotConvertLT(QTime))); connect(LocationCheckBatch, SIGNAL(clicked()), this, SLOT(slotLocationChecked())); connect(DateCheckBatch, SIGNAL(clicked()), this, SLOT(slotDateChecked())); connect(LocationCheckBatch, SIGNAL(clicked()), this, SLOT(slotHelpLabel())); connect(DateCheckBatch, SIGNAL(clicked()), this, SLOT(slotHelpLabel())); connect(ComputeComboBatch, SIGNAL(currentIndexChanged(int)), this, SLOT(slotHelpLabel())); connect(InputFileBatch, SIGNAL(urlSelected(QUrl)), this, SLOT(slotCheckFiles())); connect(OutputFileBatch, SIGNAL(urlSelected(QUrl)), this, SLOT(slotCheckFiles())); connect(LocationButtonBatch, SIGNAL(clicked()), this, SLOT(slotLocationBatch())); connect(RunButtonBatch, SIGNAL(clicked()), this, SLOT(slotRunBatch())); connect(ViewButtonBatch, SIGNAL(clicked()), this, SLOT(slotViewBatch())); RunButtonBatch->setEnabled(false); ViewButtonBatch->setEnabled(false); show(); } void modCalcSidTime::showCurrentTimeAndLocation() { KStarsData *data = KStarsData::Instance(); LT->setTime(data->lt().time()); Date->setDate(data->lt().date()); geo = data->geo(); LocationButton->setText(geo->fullName()); geoBatch = data->geo(); LocationButtonBatch->setText(geoBatch->fullName()); slotConvertST(LT->time()); } void modCalcSidTime::slotChangeLocation() { QPointer ld = new LocationDialog(this); if (ld->exec() == QDialog::Accepted) { GeoLocation *newGeo = ld->selectedCity(); if (newGeo) { geo = newGeo; LocationButton->setText(geo->fullName()); //Update the displayed ST slotConvertST(LT->time()); } } delete ld; } void modCalcSidTime::slotChangeDate() { slotConvertST(LT->time()); } void modCalcSidTime::slotConvertST(const QTime <) { // blockSignals is used to break signal loop ST->blockSignals(true); ST->setTime(computeLTtoST(lt)); ST->blockSignals(false); } void modCalcSidTime::slotConvertLT(const QTime &st) { // blockSignals is used to break signal loop LT->blockSignals(true); LT->setTime(computeSTtoLT(st)); LT->blockSignals(false); } QTime modCalcSidTime::computeLTtoST(QTime lt) { KStarsDateTime utdt = geo->LTtoUT(KStarsDateTime(Date->date(), lt)); dms st = geo->GSTtoLST(utdt.gst()); return QTime(st.hour(), st.minute(), st.second()); } QTime modCalcSidTime::computeSTtoLT(QTime st) { KStarsDateTime dt0 = KStarsDateTime(Date->date(), QTime(0, 0, 0)); dms lst; lst.setH(st.hour(), st.minute(), st.second()); dms gst = geo->LSTtoGST(lst); return geo->UTtoLT(KStarsDateTime(Date->date(), dt0.GSTtoUT(gst))).time(); } //** Batch mode **// void modCalcSidTime::slotDateChecked() { DateBatch->setEnabled(!DateCheckBatch->isChecked()); } void modCalcSidTime::slotLocationChecked() { LocationButtonBatch->setEnabled(!LocationCheckBatch->isChecked()); if (LocationCheckBatch->isChecked()) { QString message = i18n("Location strings consist of the " "comma-separated names of the city, province and country. " "If the string contains spaces, enclose it in quotes so it " "gets parsed properly."); - KMessageBox::information(0, message, i18n("Hint for writing location strings"), + KMessageBox::information(nullptr, message, i18n("Hint for writing location strings"), "DontShowLocationStringMessageBox"); } } void modCalcSidTime::slotHelpLabel() { QStringList inList; if (ComputeComboBatch->currentIndex() == 0) inList.append(i18n("local time")); else inList.append(i18n("sidereal time")); if (DateCheckBatch->checkState() == Qt::Checked) inList.append(i18n("date")); if (LocationCheckBatch->checkState() == Qt::Checked) inList.append(i18n("location")); QString inListString = inList[0]; if (inList.size() == 2) inListString = i18n("%1 and %2", inList[0], inList[1]); if (inList.size() == 3) inListString = i18n("%1, %2 and %3", inList[0], inList[1], inList[2]); HelpLabel->setText(i18n("Specify %1 in the input file.", inListString)); } void modCalcSidTime::slotLocationBatch() { QPointer ld = new LocationDialog(this); if (ld->exec() == QDialog::Accepted) { GeoLocation *newGeo = ld->selectedCity(); if (newGeo) { geoBatch = newGeo; LocationButtonBatch->setText(geoBatch->fullName()); } } delete ld; } void modCalcSidTime::slotCheckFiles() { if (!InputFileBatch->lineEdit()->text().isEmpty() && !OutputFileBatch->lineEdit()->text().isEmpty()) { RunButtonBatch->setEnabled(true); } else { RunButtonBatch->setEnabled(false); } } void modCalcSidTime::slotRunBatch() { QString inputFileName = InputFileBatch->url().toLocalFile(); if (QFile::exists(inputFileName)) { QFile f(inputFileName); if (!f.open(QIODevice::ReadOnly)) { QString message = i18n("Could not open file %1.", f.fileName()); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); inputFileName.clear(); return; } QTextStream istream(&f); processLines(istream); ViewButtonBatch->setEnabled(true); f.close(); } else { QString message = i18n("Invalid file: %1", inputFileName); - KMessageBox::sorry(0, message, i18n("Invalid file")); + KMessageBox::sorry(nullptr, message, i18n("Invalid file")); inputFileName.clear(); return; } } void modCalcSidTime::processLines(QTextStream &istream) { QFile fOut(OutputFileBatch->url().toLocalFile()); fOut.open(QIODevice::WriteOnly); QTextStream ostream(&fOut); QString line; dms LST; QTime inTime, outTime; QDate dt; if (!DateCheckBatch->isChecked()) dt = DateBatch->date(); while (!istream.atEnd()) { line = istream.readLine(); line = line.trimmed(); QStringList fields = line.split(' ', QString::SkipEmptyParts); //Find and parse the location string if (LocationCheckBatch->isChecked()) { //First, look for a pair of quotation marks, and parse the string between them QChar q = '\"'; if (line.indexOf(q) == -1) q = '\''; if (line.count(q) == 2) { int iStart = line.indexOf(q); int iEnd = line.indexOf(q, iStart + 1); QString locationString = line.mid(iStart, iEnd - iStart + 1); line.remove(locationString); fields = line.split(' ', QString::SkipEmptyParts); locationString.remove(q); QStringList locationFields = locationString.split(',', QString::SkipEmptyParts); for (int i = 0; i < locationFields.size(); i++) locationFields[i] = locationFields[i].trimmed(); if (locationFields.size() == 1) locationFields.insert(1, ""); if (locationFields.size() == 2) locationFields.insert(1, ""); if (locationFields.size() != 3) { qDebug() << "Error: could not parse location string: " << locationString; continue; } geoBatch = KStarsData::Instance()->locationNamed(locationFields[0], locationFields[1], locationFields[2]); if (geoBatch == nullptr) { qDebug() << "Error: location not found in database: " << locationString; continue; } } } if (DateCheckBatch->isChecked()) { //Parse one of the fields as the date for (auto &s : fields) { dt = QDate::fromString(s); if (dt.isValid()) break; } if (!dt.isValid()) { qDebug() << "Error: did not find a valid date string in: " << line; continue; } } //Parse one of the fields as the time for (auto &s : fields) { if (s.contains(':')) { inTime = QTime::fromString(s.length() == 4 ? '0' + s : s); if (inTime.isValid()) break; } } if (!inTime.isValid()) { qDebug() << "Error: did not find a valid time string in: " << line; continue; } if (geoBatch != nullptr) { if (ComputeComboBatch->currentIndex() == 0) { //inTime is the local time, compute LST KStarsDateTime ksdt(dt, inTime); ksdt = geoBatch->LTtoUT(ksdt); dms lst = geoBatch->GSTtoLST(ksdt.gst()); outTime = QTime(lst.hour(), lst.minute(), lst.second()); } else { //inTime is the sidereal time, compute the local time KStarsDateTime ksdt(dt, QTime(0, 0, 0)); dms lst; lst.setH(inTime.hour(), inTime.minute(), inTime.second()); QTime ut = ksdt.GSTtoUT(geoBatch->LSTtoGST(lst)); ksdt.setTime(ut); ksdt = geoBatch->UTtoLT(ksdt); outTime = ksdt.time(); } } //Write to output file ostream << QLocale().toString(dt, QLocale::LongFormat) << " \"" << geoBatch->fullName() << "\" " << QLocale().toString(inTime) << " " << QLocale().toString(outTime) << endl; } fOut.close(); } void modCalcSidTime::slotViewBatch() { QFile fOut(OutputFileBatch->url().toLocalFile()); fOut.open(QIODevice::ReadOnly); QTextStream istream(&fOut); QStringList text; while (!istream.atEnd()) text.append(istream.readLine()); fOut.close(); - KMessageBox::informationList(0, i18n("Results of Sidereal time calculation"), text, + KMessageBox::informationList(nullptr, i18n("Results of Sidereal time calculation"), text, OutputFileBatch->url().toLocalFile()); } diff --git a/kstars/tools/modcalcvizequinox.cpp b/kstars/tools/modcalcvizequinox.cpp index 6844fb7f7..ae61dd6f7 100644 --- a/kstars/tools/modcalcvizequinox.cpp +++ b/kstars/tools/modcalcvizequinox.cpp @@ -1,383 +1,383 @@ /*************************************************************************** modcalcvizequinox.cpp - description ------------------- begin : Thu 22 Feb 2007 copyright : (C) 2007 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 "modcalcvizequinox.h" #include "dms.h" #include "kstarsdata.h" #include "skyobjects/kssun.h" #include "widgets/dmsbox.h" #include #include #include #include #include modCalcEquinox::modCalcEquinox(QWidget *parentSplit) : QFrame(parentSplit), dSpring(), dSummer(), dAutumn(), dWinter() { setupUi(this); connect(Year, SIGNAL(valueChanged(int)), this, SLOT(slotCompute())); connect(InputFileBatch, SIGNAL(urlSelected(QUrl)), this, SLOT(slotCheckFiles())); connect(OutputFileBatch, SIGNAL(urlSelected(QUrl)), this, SLOT(slotCheckFiles())); connect(RunButtonBatch, SIGNAL(clicked()), this, SLOT(slotRunBatch())); connect(ViewButtonBatch, SIGNAL(clicked()), this, SLOT(slotViewBatch())); Plot->axis(KPlotWidget::LeftAxis)->setLabel(i18n("Sun's Declination")); Plot->setTopPadding(40); //Don't draw Top & Bottom axes; custom axes drawn as plot objects Plot->axis(KPlotWidget::BottomAxis)->setVisible(false); Plot->axis(KPlotWidget::TopAxis)->setVisible(false); //This will call slotCompute(): Year->setValue(KStarsData::Instance()->lt().date().year()); RunButtonBatch->setEnabled(false); ViewButtonBatch->setEnabled(false); show(); } modCalcEquinox::~modCalcEquinox() { } double modCalcEquinox::dmonth(int i) { Q_ASSERT(i >= 0 && i < 12 && "Month must be in 0 .. 11 range"); return DMonth[i]; } void modCalcEquinox::slotCheckFiles() { RunButtonBatch->setEnabled(!InputFileBatch->lineEdit()->text().isEmpty() && !OutputFileBatch->lineEdit()->text().isEmpty()); } void modCalcEquinox::slotRunBatch() { QString inputFileName = InputFileBatch->url().toLocalFile(); if (QFile::exists(inputFileName)) { QFile f(inputFileName); if (!f.open(QIODevice::ReadOnly)) { QString message = i18n("Could not open file %1.", f.fileName()); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); inputFileName.clear(); return; } QTextStream istream(&f); processLines(istream); ViewButtonBatch->setEnabled(true); f.close(); } else { QString message = i18n("Invalid file: %1", inputFileName); - KMessageBox::sorry(0, message, i18n("Invalid file")); + KMessageBox::sorry(nullptr, message, i18n("Invalid file")); inputFileName.clear(); return; } } void modCalcEquinox::processLines(QTextStream &istream) { QFile fOut(OutputFileBatch->url().toLocalFile()); fOut.open(QIODevice::WriteOnly); QTextStream ostream(&fOut); int originalYear = Year->value(); //Write header to output file ostream << i18n("# Timing of Equinoxes and Solstices\n") << i18n("# computed by KStars\n#\n") << i18n("# Vernal Equinox\t\tSummer Solstice\t\t\tAutumnal Equinox\t\tWinter Solstice\n#\n"); while (!istream.atEnd()) { QString line = istream.readLine(); bool ok = false; int year = line.toInt(&ok); //for now I will simply change the value of the Year widget to trigger //computation of the Equinoxes and Solstices. if (ok) { //triggers slotCompute(), which sets values of dSpring et al.: Year->setValue(year); //Write to output file ostream << QLocale().toString(dSpring.date(), QLocale::LongFormat) << "\t" << QLocale().toString(dSummer.date(), QLocale::LongFormat) << "\t" << QLocale().toString(dAutumn.date(), QLocale::LongFormat) << "\t" << QLocale().toString(dWinter.date(), QLocale::LongFormat) << endl; } } if (Year->value() != originalYear) Year->setValue(originalYear); } void modCalcEquinox::slotViewBatch() { QFile fOut(OutputFileBatch->url().toLocalFile()); fOut.open(QIODevice::ReadOnly); QTextStream istream(&fOut); QStringList text; while (!istream.atEnd()) text.append(istream.readLine()); fOut.close(); - KMessageBox::informationList(0, i18n("Results of Sidereal time calculation"), text, + KMessageBox::informationList(nullptr, i18n("Results of Sidereal time calculation"), text, OutputFileBatch->url().toLocalFile()); } void modCalcEquinox::slotCompute() { KStarsData *data = KStarsData::Instance(); KSSun Sun; int year0 = Year->value(); KStarsDateTime dt(QDate(year0, 1, 1), QTime(0, 0, 0)); long double jd0 = dt.djd(); //save JD on Jan 1st for (int imonth = 0; imonth < 12; imonth++) { KStarsDateTime kdt(QDate(year0, imonth + 1, 1), QTime(0, 0, 0)); DMonth[imonth] = kdt.djd() - jd0; } Plot->removeAllPlotObjects(); //Add the celestial equator, just a single line bisecting the plot horizontally KPlotObject *ce = new KPlotObject(data->colorScheme()->colorNamed("EqColor"), KPlotObject::Lines, 2.0); ce->addPoint(0.0, 0.0); ce->addPoint(366.0, 0.0); Plot->addPlotObject(ce); // Tropic of cancer KPlotObject *tcan = new KPlotObject(data->colorScheme()->colorNamed("EqColor"), KPlotObject::Lines, 2.0); tcan->addPoint(0.0, 23.5); tcan->addPoint(366.0, 23.5); Plot->addPlotObject(tcan); // Tropic of capricorn KPlotObject *tcap = new KPlotObject(data->colorScheme()->colorNamed("EqColor"), KPlotObject::Lines, 2.0); tcap->addPoint(0.0, -23.5); tcap->addPoint(366.0, -23.5); Plot->addPlotObject(tcap); //Add Ecliptic. This is more complicated than simply incrementing the //ecliptic longitude, because we want the x-axis to be time, not RA. //For each day in the year, compute the Sun's position. KPlotObject *ecl = new KPlotObject(data->colorScheme()->colorNamed("EclColor"), KPlotObject::Lines, 2); ecl->setLinePen(QPen(ecl->pen().color(), 4)); Plot->setLimits(1.0, double(dt.date().daysInYear()), -30.0, 30.0); //Add top and bottom axis lines, and custom tickmarks at each month addDateAxes(); for (int i = 1; i <= dt.date().daysInYear(); i++) { KSNumbers num(dt.djd()); Sun.findPosition(&num); ecl->addPoint(double(i), Sun.dec().Degrees()); dt = dt.addDays(1); } Plot->addPlotObject(ecl); dSpring = findEquinox(Year->value(), true, ecl); dSummer = findSolstice(Year->value(), true); dAutumn = findEquinox(Year->value(), false, ecl); dWinter = findSolstice(Year->value(), false); //Display the Date/Time of each event in the text fields VEquinox->setText(QLocale().toString(dSpring, QLocale::LongFormat)); SSolstice->setText(QLocale().toString(dSummer, QLocale::LongFormat)); AEquinox->setText(QLocale().toString(dAutumn, QLocale::LongFormat)); WSolstice->setText(QLocale().toString(dWinter, QLocale::LongFormat)); //Add vertical dotted lines at times of the equinoxes and solstices KPlotObject *poSpring = new KPlotObject(Qt::white, KPlotObject::Lines, 1); poSpring->setLinePen(QPen(Qt::white, 1.0, Qt::DotLine)); poSpring->addPoint(dSpring.djd() - jd0, Plot->dataRect().top()); poSpring->addPoint(dSpring.djd() - jd0, Plot->dataRect().bottom()); Plot->addPlotObject(poSpring); KPlotObject *poSummer = new KPlotObject(Qt::white, KPlotObject::Lines, 1); poSummer->setLinePen(QPen(Qt::white, 1.0, Qt::DotLine)); poSummer->addPoint(dSummer.djd() - jd0, Plot->dataRect().top()); poSummer->addPoint(dSummer.djd() - jd0, Plot->dataRect().bottom()); Plot->addPlotObject(poSummer); KPlotObject *poAutumn = new KPlotObject(Qt::white, KPlotObject::Lines, 1); poAutumn->setLinePen(QPen(Qt::white, 1.0, Qt::DotLine)); poAutumn->addPoint(dAutumn.djd() - jd0, Plot->dataRect().top()); poAutumn->addPoint(dAutumn.djd() - jd0, Plot->dataRect().bottom()); Plot->addPlotObject(poAutumn); KPlotObject *poWinter = new KPlotObject(Qt::white, KPlotObject::Lines, 1); poWinter->setLinePen(QPen(Qt::white, 1.0, Qt::DotLine)); poWinter->addPoint(dWinter.djd() - jd0, Plot->dataRect().top()); poWinter->addPoint(dWinter.djd() - jd0, Plot->dataRect().bottom()); Plot->addPlotObject(poWinter); } //Add custom top/bottom axes with tickmarks for each month void modCalcEquinox::addDateAxes() { KPlotObject *poTopAxis = new KPlotObject(Qt::white, KPlotObject::Lines, 1); poTopAxis->addPoint(0.0, Plot->dataRect().bottom()); //y-axis is reversed! poTopAxis->addPoint(366.0, Plot->dataRect().bottom()); Plot->addPlotObject(poTopAxis); KPlotObject *poBottomAxis = new KPlotObject(Qt::white, KPlotObject::Lines, 1); poBottomAxis->addPoint(0.0, Plot->dataRect().top() + 0.02); poBottomAxis->addPoint(366.0, Plot->dataRect().top() + 0.02); Plot->addPlotObject(poBottomAxis); //Tick mark for each month for (int imonth = 0; imonth < 12; imonth++) { KPlotObject *poMonth = new KPlotObject(Qt::white, KPlotObject::Lines, 1); poMonth->addPoint(dmonth(imonth), Plot->dataRect().top()); poMonth->addPoint(dmonth(imonth), Plot->dataRect().top() + 1.4); Plot->addPlotObject(poMonth); poMonth = new KPlotObject(Qt::white, KPlotObject::Lines, 1); poMonth->addPoint(dmonth(imonth), Plot->dataRect().bottom()); poMonth->addPoint(dmonth(imonth), Plot->dataRect().bottom() - 1.4); Plot->addPlotObject(poMonth); } } KStarsDateTime modCalcEquinox::findEquinox(int year, bool Spring, KPlotObject *ecl) { // Interpolate to find the moment when the Sun crosses the equator // Set initial guess in February or August to be sure that this // point is before equinox. const int month = Spring ? 2 : 8; int i = QDate(year, month, 1).dayOfYear(); double dec1, dec2; dec2 = ecl->points().at(i)->y(); do { ++i; dec1 = dec2; dec2 = ecl->points().at(i)->y(); } while (dec1 * dec2 > 0.0); //when dec1*dec2<0.0, we bracket the zero double x1 = ecl->points().at(i-1)->x(); double x2 = ecl->points().at(i)->x(); double d = fabs(dec2 - dec1); double f = 1.0 - fabs(dec2) / d; //fractional distance of the zero, from point1 to point2 KStarsDateTime dt0(QDate(year, 1, 1), QTime(0, 0, 0)); KStarsDateTime dt = dt0.addSecs(86400.0 * (x1 - 1 + f * (x2 - x1))); return dt; } KStarsDateTime modCalcEquinox::findSolstice(int year, bool Summer) { //Find the moment when the Sun reaches maximum declination //First find three points which bracket the maximum (i.e., x2 > x1,x3) //Start at June 16th, which will always be approaching the solstice long double jd1, jd2, jd3, jd4; double y2(0.0), y3(0.0), y4(0.0); int month = 6; if (!Summer) month = 12; jd3 = KStarsDateTime(QDate(year, month, 16), QTime(0, 0, 0)).djd(); KSNumbers num(jd3); KSSun Sun; Sun.findPosition(&num); y3 = Sun.dec().Degrees(); int sgn = 1; if (!Summer) sgn = -1; //find minimum if the winter solstice is sought do { jd3 += 1.0; num.updateValues(jd3); Sun.findPosition(&num); y2 = y3; Sun.findPosition(&num); y3 = Sun.dec().Degrees(); } while (y3 * sgn > y2 * sgn); //Ok, now y2 is larger(smaller) than both y3 and y1. // Never read back // jd2 = jd3 - 1.0; jd1 = jd3 - 2.0; //Choose a new starting jd2 that follows the golden ratio: // a/b = 1.618; a+b = 2...a = 0.76394 jd2 = jd1 + 0.76394; num.updateValues(jd2); Sun.findPosition(&num); y2 = Sun.dec().Degrees(); while (jd3 - jd1 > 0.0005) //sub-minute pecision { jd4 = jd1 + jd3 - jd2; num.updateValues(jd4); Sun.findPosition(&num); y4 = Sun.dec().Degrees(); if (y4 * sgn > y2 * sgn) //make jd4 the new center { if (jd4 > jd2) { jd1 = jd2; jd2 = jd4; y2 = y4; } else { jd3 = jd2; // Never read back // y3 = y2; jd2 = jd4; y2 = y4; } } else //make jd4 a new endpoint { if (jd4 > jd2) { jd3 = jd4; // Never read back // y3 = y4; } else { jd1 = jd4; } } } return KStarsDateTime(jd2); } diff --git a/kstars/tools/modcalcvlsr.cpp b/kstars/tools/modcalcvlsr.cpp index 877720142..a74f12091 100644 --- a/kstars/tools/modcalcvlsr.cpp +++ b/kstars/tools/modcalcvlsr.cpp @@ -1,514 +1,514 @@ /*************************************************************************** modcalcvlsr.cpp - description ------------------- begin : sun mar 13 2005 copyright : (C) 2005 by Pablo de Vicente email : p.devicente@wanadoo.es ***************************************************************************/ /*************************************************************************** * * * 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 "modcalcvlsr.h" #include #include #include #include #include "ksnumbers.h" #include "dms.h" #include "skyobjects/skypoint.h" #include "geolocation.h" #include "kstars.h" #include "kstarsdata.h" #include "kstarsdatetime.h" #include "widgets/dmsbox.h" #include "dialogs/locationdialog.h" #include "dialogs/finddialog.h" modCalcVlsr::modCalcVlsr(QWidget *parentSplit) : QFrame(parentSplit), velocityFlag(0) { setupUi(this); RA->setDegType(false); Date->setDateTime(KStarsDateTime::currentDateTime()); initGeo(); VLSR->setValidator(new QDoubleValidator(VLSR)); VHelio->setValidator(new QDoubleValidator(VHelio)); VGeo->setValidator(new QDoubleValidator(VGeo)); VTopo->setValidator(new QDoubleValidator(VTopo)); // signals and slots connections connect(Date, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(slotCompute())); connect(NowButton, SIGNAL(clicked()), this, SLOT(slotNow())); connect(LocationButton, SIGNAL(clicked()), this, SLOT(slotLocation())); connect(ObjectButton, SIGNAL(clicked()), this, SLOT(slotFindObject())); connect(RA, SIGNAL(editingFinished()), this, SLOT(slotCompute())); connect(Dec, SIGNAL(editingFinished()), this, SLOT(slotCompute())); connect(VLSR, SIGNAL(editingFinished()), this, SLOT(slotCompute())); connect(VHelio, SIGNAL(editingFinished()), this, SLOT(slotCompute())); connect(VGeo, SIGNAL(editingFinished()), this, SLOT(slotCompute())); connect(VTopo, SIGNAL(editingFinished()), this, SLOT(slotCompute())); connect(RunButtonBatch, SIGNAL(clicked()), this, SLOT(slotRunBatch())); show(); } modCalcVlsr::~modCalcVlsr() { } void modCalcVlsr::initGeo(void) { geoPlace = KStarsData::Instance()->geo(); LocationButton->setText(geoPlace->fullName()); } void modCalcVlsr::slotNow() { Date->setDateTime(KStarsDateTime::currentDateTime()); slotCompute(); } void modCalcVlsr::slotFindObject() { QPointer fd = new FindDialog(KStars::Instance()); if (fd->exec() == QDialog::Accepted) { SkyObject *o = fd->targetObject(); RA->showInHours(o->ra0()); Dec->showInDegrees(o->dec0()); } delete fd; } void modCalcVlsr::slotLocation() { QPointer ld(new LocationDialog(this)); if (ld->exec() == QDialog::Accepted && ld) { GeoLocation *newGeo = ld->selectedCity(); if (newGeo) { geoPlace = newGeo; LocationButton->setText(geoPlace->fullName()); } } delete ld; slotCompute(); } void modCalcVlsr::slotCompute() { bool ok1(false), ok2(false); SkyPoint sp(RA->createDms(false, &ok1), Dec->createDms(true, &ok2)); if (!ok1 || !ok2) return; KStarsDateTime dt(Date->dateTime()); double vst[3]; geoPlace->TopocentricVelocity(vst, dt.gst()); if (sender()->objectName() == "VLSR") velocityFlag = 0; if (sender()->objectName() == "VHelio") velocityFlag = 1; if (sender()->objectName() == "VGeo") velocityFlag = 2; if (sender()->objectName() == "VTopo") velocityFlag = 3; switch (velocityFlag) { case 0: //Hold VLSR constant, compute the others { double vlsr = VLSR->text().toDouble(); double vhelio = sp.vHeliocentric(vlsr, dt.djd()); double vgeo = sp.vGeocentric(vhelio, dt.djd()); VHelio->setText(QString::number(vhelio)); VGeo->setText(QString::number(vgeo)); VTopo->setText(QString::number(sp.vTopocentric(vgeo, vst))); break; } case 1: //Hold VHelio constant, compute the others { double vhelio = VHelio->text().toDouble(); double vlsr = sp.vHelioToVlsr(vhelio, dt.djd()); double vgeo = sp.vGeocentric(vhelio, dt.djd()); VLSR->setText(QString::number(vlsr)); VGeo->setText(QString::number(vgeo)); VTopo->setText(QString::number(sp.vTopocentric(vgeo, vst))); break; } case 2: //Hold VGeo constant, compute the others { double vgeo = VGeo->text().toDouble(); double vhelio = sp.vGeoToVHelio(vgeo, dt.djd()); double vlsr = sp.vHelioToVlsr(vhelio, dt.djd()); VLSR->setText(QString::number(vlsr)); VHelio->setText(QString::number(vhelio)); VTopo->setText(QString::number(sp.vTopocentric(vgeo, vst))); break; } case 3: //Hold VTopo constant, compute the others { double vtopo = VTopo->text().toDouble(); double vgeo = sp.vTopoToVGeo(vtopo, vst); double vhelio = sp.vGeoToVHelio(vgeo, dt.djd()); double vlsr = sp.vHelioToVlsr(vhelio, dt.djd()); VLSR->setText(QString::number(vlsr)); VHelio->setText(QString::number(vhelio)); VGeo->setText(QString::number(vgeo)); break; } default: //oops qDebug() << "Error: do not know which velocity to use for input."; break; } } void modCalcVlsr::slotUtChecked() { if (UTCheckBatch->isChecked()) UTBoxBatch->setEnabled(false); else { UTBoxBatch->setEnabled(true); } } void modCalcVlsr::slotDateChecked() { if (DateCheckBatch->isChecked()) DateBoxBatch->setEnabled(false); else { DateBoxBatch->setEnabled(true); } } void modCalcVlsr::slotRaChecked() { if (RACheckBatch->isChecked()) { RABoxBatch->setEnabled(false); } else { RABoxBatch->setEnabled(true); } } void modCalcVlsr::slotDecChecked() { if (DecCheckBatch->isChecked()) { DecBoxBatch->setEnabled(false); } else { DecBoxBatch->setEnabled(true); } } void modCalcVlsr::slotEpochChecked() { if (EpochCheckBatch->isChecked()) EpochBoxBatch->setEnabled(false); else EpochBoxBatch->setEnabled(true); } void modCalcVlsr::slotLongChecked() { if (LongCheckBatch->isChecked()) LongitudeBoxBatch->setEnabled(false); else LongitudeBoxBatch->setEnabled(true); } void modCalcVlsr::slotLatChecked() { if (LatCheckBatch->isChecked()) LatitudeBoxBatch->setEnabled(false); else { LatitudeBoxBatch->setEnabled(true); } } void modCalcVlsr::slotHeightChecked() { if (ElevationCheckBatch->isChecked()) ElevationBoxBatch->setEnabled(false); else { ElevationBoxBatch->setEnabled(true); } } void modCalcVlsr::slotVlsrChecked() { if (InputVelocityCheckBatch->isChecked()) InputVelocityBoxBatch->setEnabled(false); else { InputVelocityBoxBatch->setEnabled(true); } } void modCalcVlsr::slotInputFile() { const QString inputFileName = QFileDialog::getOpenFileName(KStars::Instance(), QString(), QString()); if (!inputFileName.isEmpty()) InputFileBoxBatch->setUrl(QUrl::fromLocalFile(inputFileName)); } void modCalcVlsr::slotOutputFile() { const QString outputFileName = QFileDialog::getSaveFileName(); if (!outputFileName.isEmpty()) OutputFileBoxBatch->setUrl(QUrl::fromLocalFile(outputFileName)); } void modCalcVlsr::slotRunBatch() { const QString inputFileName = InputFileBoxBatch->url().toLocalFile(); // We open the input file and read its content if (QFile::exists(inputFileName)) { QFile f(inputFileName); if (!f.open(QIODevice::ReadOnly)) { QString message = i18n("Could not open file %1.", f.fileName()); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); return; } // processLines(&f); QTextStream istream(&f); processLines(istream); // readFile( istream ); f.close(); } else { QString message = i18n("Invalid file: %1", inputFileName); - KMessageBox::sorry(0, message, i18n("Invalid file")); + KMessageBox::sorry(nullptr, message, i18n("Invalid file")); InputFileBoxBatch->setUrl(QUrl()); } } void modCalcVlsr::processLines(QTextStream &istream) { // we open the output file // QTextStream istream(&fIn); QString outputFileName; outputFileName = OutputFileBoxBatch->url().toLocalFile(); QFile fOut(outputFileName); fOut.open(QIODevice::WriteOnly); QTextStream ostream(&fOut); QString line; QChar space = ' '; int i = 0; long double jd0; SkyPoint spB; double sra, cra, sdc, cdc; dms raB, decB, latB, longB; QString epoch0B; double vhB, vgB, vtB, vlsrB, heightB; double vtopo[3]; QTime utB; QDate dtB; KStarsDateTime dt0B; while (!istream.atEnd()) { line = istream.readLine(); line.trimmed(); //Go through the line, looking for parameters QStringList fields = line.split(' '); i = 0; // Read Ut and write in ostream if corresponds if (UTCheckBatch->isChecked()) { utB = QTime::fromString(fields[i]); i++; } else utB = UTBoxBatch->time(); if (AllRadioBatch->isChecked()) ostream << QLocale().toString(utB) << space; else if (UTCheckBatch->isChecked()) ostream << QLocale().toString(utB) << space; // Read date and write in ostream if corresponds if (DateCheckBatch->isChecked()) { dtB = QDate::fromString(fields[i]); i++; } else dtB = DateBoxBatch->date(); if (AllRadioBatch->isChecked()) ostream << QLocale().toString(dtB, QLocale::LongFormat).append(space); else if (DateCheckBatch->isChecked()) ostream << QLocale().toString(dtB, QLocale::LongFormat).append(space); // Read RA and write in ostream if corresponds if (RACheckBatch->isChecked()) { raB = dms::fromString(fields[i], false); i++; } else raB = RABoxBatch->createDms(false); if (AllRadioBatch->isChecked()) ostream << raB.toHMSString() << space; else if (RACheckBatch->isChecked()) ostream << raB.toHMSString() << space; // Read DEC and write in ostream if corresponds if (DecCheckBatch->isChecked()) { decB = dms::fromString(fields[i], true); i++; } else decB = DecBoxBatch->createDms(); if (AllRadioBatch->isChecked()) ostream << decB.toDMSString() << space; else if (DecCheckBatch->isChecked()) ostream << decB.toDMSString() << space; // Read Epoch and write in ostream if corresponds if (EpochCheckBatch->isChecked()) { epoch0B = fields[i]; i++; } else epoch0B = EpochBoxBatch->text(); if (AllRadioBatch->isChecked()) ostream << epoch0B << space; else if (EpochCheckBatch->isChecked()) ostream << epoch0B << space; // Read vlsr and write in ostream if corresponds if (InputVelocityCheckBatch->isChecked()) { vlsrB = fields[i].toDouble(); i++; } else vlsrB = InputVelocityComboBatch->currentText().toDouble(); if (AllRadioBatch->isChecked()) ostream << vlsrB << space; else if (InputVelocityCheckBatch->isChecked()) ostream << vlsrB << space; // Read Longitude and write in ostream if corresponds if (LongCheckBatch->isChecked()) { longB = dms::fromString(fields[i], true); i++; } else longB = LongitudeBoxBatch->createDms(true); if (AllRadioBatch->isChecked()) ostream << longB.toDMSString() << space; else if (LongCheckBatch->isChecked()) ostream << longB.toDMSString() << space; // Read Latitude if (LatCheckBatch->isChecked()) { latB = dms::fromString(fields[i], true); i++; } else latB = LatitudeBoxBatch->createDms(true); if (AllRadioBatch->isChecked()) ostream << latB.toDMSString() << space; else if (LatCheckBatch->isChecked()) ostream << latB.toDMSString() << space; // Read height and write in ostream if corresponds if (ElevationCheckBatch->isChecked()) { heightB = fields[i].toDouble(); i++; } else heightB = ElevationBoxBatch->text().toDouble(); if (AllRadioBatch->isChecked()) ostream << heightB << space; else if (ElevationCheckBatch->isChecked()) ostream << heightB << space; // We make the first calculations spB = SkyPoint(raB, decB); dt0B.setFromEpoch(epoch0B); vhB = spB.vHeliocentric(vlsrB, dt0B.djd()); jd0 = KStarsDateTime(dtB, utB).djd(); vgB = spB.vGeocentric(vlsrB, jd0); geoPlace->setLong(longB); geoPlace->setLat(latB); geoPlace->setHeight(heightB); dms gsidt = KStarsDateTime(dtB, utB).gst(); geoPlace->TopocentricVelocity(vtopo, gsidt); spB.ra().SinCos(sra, cra); spB.dec().SinCos(sdc, cdc); vtB = vgB - (vtopo[0] * cdc * cra + vtopo[1] * cdc * sra + vtopo[2] * sdc); ostream << vhB << space << vgB << space << vtB << endl; } fOut.close(); } diff --git a/kstars/tools/observinglist.cpp b/kstars/tools/observinglist.cpp index 33cdb7361..c6fe81a0a 100644 --- a/kstars/tools/observinglist.cpp +++ b/kstars/tools/observinglist.cpp @@ -1,1675 +1,1675 @@ /*************************************************************************** observinglist.cpp - K Desktop Planetarium ------------------- begin : 29 Nov 2004 copyright : (C) 2004-2014 by Jeff Woods, Jason Harris, Prakash Mohan, Akarsh Simha email : jcwoods@bellsouth.net, jharris@30doradus.org, prakash.mohan@kdemail.net, akarsh@kde.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 "observinglist.h" #include "config-kstars.h" #include "constellationboundarylines.h" #include "fov.h" #include "imageviewer.h" #include "ksalmanac.h" #include "ksdssdownloader.h" #include "kspaths.h" #include "kstars.h" #include "kstarsdata.h" #include "ksutils.h" #include "obslistpopupmenu.h" #include "obslistwizard.h" #include "Options.h" #include "sessionsortfilterproxymodel.h" #include "skymap.h" #include "thumbnailpicker.h" #include "dialogs/detaildialog.h" #include "dialogs/finddialog.h" #include "dialogs/locationdialog.h" #include "oal/execute.h" #include "skycomponents/skymapcomposite.h" #include "skyobjects/starobject.h" #include "tools/altvstime.h" #include "tools/eyepiecefield.h" #include "tools/wutdialog.h" #ifdef HAVE_INDI #include #include "indi/indilistener.h" #include "indi/drivermanager.h" #include "indi/driverinfo.h" #include "ekos/ekosmanager.h" #endif #include #include #include // // ObservingListUI // --------------------------------- ObservingListUI::ObservingListUI(QWidget *p) : QFrame(p) { setupUi(this); } // // ObservingList // --------------------------------- ObservingList::ObservingList() - : QDialog((QWidget *)KStars::Instance()), LogObject(0), m_CurrentObject(0), isModified(false), m_dl(0) + : QDialog((QWidget *)KStars::Instance()), LogObject(nullptr), m_CurrentObject(nullptr), isModified(false), m_dl(nullptr) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif ui = new ObservingListUI(this); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(ui); setWindowTitle(i18n("Observation Planner")); setLayout(mainLayout); dt = KStarsDateTime::currentDateTime(); setFocusPolicy(Qt::StrongFocus); geo = KStarsData::Instance()->geo(); sessionView = false; m_listFileName = QString(); pmenu.reset(new ObsListPopupMenu()); //Set up the Table Views m_WishListModel.reset(new QStandardItemModel(0, 5, this)); m_SessionModel.reset(new QStandardItemModel(0, 5)); m_WishListModel->setHorizontalHeaderLabels( QStringList() << i18n("Name") << i18n("Alternate Name") << i18nc("Right Ascension", "RA (J2000)") << i18nc("Declination", "Dec (J2000)") << i18nc("Magnitude", "Mag") << i18n("Type") << i18n("Current Altitude")); m_SessionModel->setHorizontalHeaderLabels( QStringList() << i18n("Name") << i18n("Alternate Name") << i18nc("Right Ascension", "RA (J2000)") << i18nc("Declination", "Dec (J2000)") << i18nc("Magnitude", "Mag") << i18n("Type") << i18nc("Constellation", "Constell.") << i18n("Time") << i18nc("Altitude", "Alt") << i18nc("Azimuth", "Az")); m_WishListSortModel.reset(new QSortFilterProxyModel(this)); m_WishListSortModel->setSourceModel(m_WishListModel.get()); m_WishListSortModel->setDynamicSortFilter(true); m_WishListSortModel->setSortRole(Qt::UserRole); ui->WishListView->setModel(m_WishListSortModel.get()); ui->WishListView->horizontalHeader()->setStretchLastSection(true); ui->WishListView->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); m_SessionSortModel.reset(new SessionSortFilterProxyModel()); m_SessionSortModel->setSourceModel(m_SessionModel.get()); m_SessionSortModel->setDynamicSortFilter(true); ui->SessionView->setModel(m_SessionSortModel.get()); ui->SessionView->horizontalHeader()->setStretchLastSection(true); ui->SessionView->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); ksal.reset(new KSAlmanac); ksal->setLocation(geo); ui->avt->setGeoLocation(geo); ui->avt->setSunRiseSetTimes(ksal->getSunRise(), ksal->getSunSet()); ui->avt->setLimits(-12.0, 12.0, -90.0, 90.0); ui->avt->axis(KPlotWidget::BottomAxis)->setTickLabelFormat('t'); ui->avt->axis(KPlotWidget::BottomAxis)->setLabel(i18n("Local Time")); ui->avt->axis(KPlotWidget::TopAxis)->setTickLabelFormat('t'); ui->avt->axis(KPlotWidget::TopAxis)->setTickLabelsShown(true); ui->DateEdit->setDate(dt.date()); ui->SetLocation->setText(geo->fullName()); ui->ImagePreview->installEventFilter(this); ui->WishListView->viewport()->installEventFilter(this); ui->WishListView->installEventFilter(this); ui->SessionView->viewport()->installEventFilter(this); ui->SessionView->installEventFilter(this); // setDefaultImage(); //Connections connect(ui->WishListView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotCenterObject())); connect(ui->WishListView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotNewSelection())); connect(ui->SessionView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotNewSelection())); connect(ui->WUTButton, SIGNAL(clicked()), this, SLOT(slotWUT())); connect(ui->FindButton, SIGNAL(clicked()), this, SLOT(slotFind())); connect(ui->OpenButton, SIGNAL(clicked()), this, SLOT(slotOpenList())); connect(ui->SaveButton, SIGNAL(clicked()), this, SLOT(slotSaveSession())); connect(ui->SaveAsButton, SIGNAL(clicked()), this, SLOT(slotSaveSessionAs())); connect(ui->WizardButton, SIGNAL(clicked()), this, SLOT(slotWizard())); connect(ui->SetLocation, SIGNAL(clicked()), this, SLOT(slotLocation())); connect(ui->Update, SIGNAL(clicked()), this, SLOT(slotUpdate())); connect(ui->DeleteImage, SIGNAL(clicked()), this, SLOT(slotDeleteCurrentImage())); connect(ui->SearchImage, SIGNAL(clicked()), this, SLOT(slotSearchImage())); connect(ui->SetTime, SIGNAL(clicked()), this, SLOT(slotSetTime())); connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(slotChangeTab(int))); connect(ui->saveImages, SIGNAL(clicked()), this, SLOT(slotSaveAllImages())); connect(ui->DeleteAllImages, SIGNAL(clicked()), this, SLOT(slotDeleteAllImages())); connect(ui->OALExport, SIGNAL(clicked()), this, SLOT(slotOALExport())); connect(ui->clearListB, SIGNAL(clicked()), this, SLOT(slotClearList())); //Add icons to Push Buttons ui->OpenButton->setIcon(QIcon::fromTheme("document-open", QIcon(":/icons/breeze/default/document-open.svg"))); ui->OpenButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->SaveButton->setIcon(QIcon::fromTheme("document-save", QIcon(":/icons/breeze/default/document-save.svg"))); ui->SaveButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->SaveAsButton->setIcon( QIcon::fromTheme("document-save-as", QIcon(":/icons/breeze/default/document-save-as.svg"))); ui->SaveAsButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); ui->WizardButton->setIcon(QIcon::fromTheme("tools-wizard", QIcon(":/icons/breeze/default/tools-wizard.svg"))); ui->WizardButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); noSelection = true; showScope = false; ui->NotesEdit->setEnabled(false); ui->SetTime->setEnabled(false); ui->TimeEdit->setEnabled(false); ui->SearchImage->setEnabled(false); ui->saveImages->setEnabled(false); ui->DeleteImage->setEnabled(false); ui->OALExport->setEnabled(false); m_NoImagePixmap = QPixmap(":/images/noimage.png") .scaled(ui->ImagePreview->width(), ui->ImagePreview->height(), Qt::KeepAspectRatio, Qt::FastTransformation); m_altCostHelper = [this](const SkyPoint &p) -> QStandardItem * { const double inf = std::numeric_limits::infinity(); double altCost = 0.; QString itemText; double maxAlt = p.maxAlt(*(geo->lat())); if (Options::obsListDemoteHole() && maxAlt > 90. - Options::obsListHoleSize()) maxAlt = 90. - Options::obsListHoleSize(); if (maxAlt <= 0.) { altCost = -inf; itemText = i18n("Never rises"); } else { altCost = (p.alt().Degrees() / maxAlt) * 100.; if (altCost < 0) itemText = i18nc("Short text to describe that object has not risen yet", "Not risen"); else { if (altCost > 100.) { altCost = -inf; itemText = i18nc("Object is in the Dobsonian hole", "In hole"); } else itemText = QString::number(altCost, 'f', 0) + '%'; } } QStandardItem *altItem = new QStandardItem(itemText); altItem->setData(altCost, Qt::UserRole); // qCDebug(KSTARS) << "Updating altitude for " << p.ra().toHMSString() << " " << p.dec().toDMSString() << " alt = " << p.alt().toDMSString() << " info to " << itemText; return altItem; }; // Needed to fix weird bug on Windows that started with Qt 5.9 that makes the title bar // not visible and therefore dialog not movable. #ifdef Q_OS_WIN move(100,100); #endif } ObservingList::~ObservingList() { } // Show Event void ObservingList::showEvent(QShowEvent *) { // ONLY run for first ever load if (m_initialWishlistLoad == false) { m_initialWishlistLoad = true; slotLoadWishList(); //Load the wishlist from disk if present - m_CurrentObject = 0; + m_CurrentObject = nullptr; setSaveImagesButton(); slotUpdateAltitudes(); m_altitudeUpdater = new QTimer(this); connect(m_altitudeUpdater, SIGNAL(timeout()), this, SLOT(slotUpdateAltitudes())); m_altitudeUpdater->start(120000); // update altitudes every 2 minutes } } //SLOTS void ObservingList::slotAddObject(const SkyObject *_obj, bool session, bool update) { bool addToWishList = true; if (!_obj) _obj = SkyMap::Instance()->clickedObject(); // Eh? Why? Weird default behavior. if (!_obj) { qCWarning(KSTARS) << "Trying to add null object to observing list! Ignoring."; return; } QString finalObjectName = getObjectName(_obj); if (finalObjectName.isEmpty()) { - KMessageBox::sorry(0, i18n("Unnamed stars are not supported in the observing lists")); + KMessageBox::sorry(nullptr, i18n("Unnamed stars are not supported in the observing lists")); return; } //First, make sure object is not already in the list QSharedPointer obj = findObject(_obj); if (obj) { addToWishList = false; if (!session) { KStars::Instance()->statusBar()->showMessage( i18n("%1 is already in your wishlist.", finalObjectName), 0); // FIXME: This message is too inconspicuous if using the Find dialog to add return; } } else { assert(!findObject(_obj, session)); qCDebug(KSTARS) << "Cloned object " << finalObjectName << " to add to observing list."; obj = QSharedPointer( _obj->clone()); // Use a clone in case the original SkyObject is deleted due to change in catalog configuration. } if (session && sessionList().contains(obj)) { KStars::Instance()->statusBar()->showMessage(i18n("%1 is already in the session plan.", finalObjectName), 0); return; } // JM: If we are loading observing list from disk, solar system objects magnitudes are not calculated until later // Therefore, we manual invoke updateCoords to force computation of magnitude. if ((obj->type() == SkyObject::COMET || obj->type() == SkyObject::ASTEROID || obj->type() == SkyObject::MOON || obj->type() == SkyObject::PLANET) && obj->mag() == 0) { KSNumbers num(dt.djd()); CachingDms LST = geo->GSTtoLST(dt.gst()); obj->updateCoords(&num, true, geo->lat(), &LST, true); } QString smag = "--"; if (-30.0 < obj->mag() && obj->mag() < 90.0) smag = QString::number(obj->mag(), 'f', 2); // The lower limit to avoid display of unrealistic comet magnitudes SkyPoint p = obj->recomputeHorizontalCoords(dt, geo); QList itemList; auto getItemWithUserRole = [](const QString &itemText) -> QStandardItem * { QStandardItem *ret = new QStandardItem(itemText); ret->setData(itemText, Qt::UserRole); return ret; }; // Fill itemlist with items that are common to both wishlist additions and session plan additions auto populateItemList = [&getItemWithUserRole, &itemList, &finalObjectName, obj, &p, &smag]() { itemList.clear(); QStandardItem *keyItem = getItemWithUserRole(finalObjectName); keyItem->setData(QVariant::fromValue(static_cast(obj.data())), Qt::UserRole + 1); itemList << keyItem // NOTE: The rest of the methods assume that the SkyObject pointer is available in the first column! << getItemWithUserRole(obj->translatedLongName()) << getItemWithUserRole(p.ra0().toHMSString()) << getItemWithUserRole(p.dec0().toDMSString()) << getItemWithUserRole(smag) << getItemWithUserRole(obj->typeName()); }; //Insert object in the Wish List if (addToWishList) { m_WishList.append(obj); m_CurrentObject = obj.data(); //QString ra, dec; //ra = "";//p.ra().toHMSString(); //dec = p.dec().toDMSString(); populateItemList(); // FIXME: Instead sort by a "clever" observability score, calculated as follows: // - First sort by (max altitude) - (current altitude) rounded off to the nearest // - Weight by declination - latitude (in the northern hemisphere, southern objects get higher precedence) // - Demote objects in the hole SkyPoint p = obj->recomputeHorizontalCoords(KStarsDateTime::currentDateTimeUtc(), geo); // Current => now itemList << m_altCostHelper(p); m_WishListModel->appendRow(itemList); //Note addition in statusbar KStars::Instance()->statusBar()->showMessage(i18n("Added %1 to observing list.", finalObjectName), 0); ui->WishListView->resizeColumnsToContents(); if (!update) slotSaveList(); } //Insert object in the Session List if (session) { m_SessionList.append(obj); dt.setTime(TimeHash.value(finalObjectName, obj->transitTime(dt, geo))); dms lst(geo->GSTtoLST(dt.gst())); p.EquatorialToHorizontal(&lst, geo->lat()); QString alt = "--", az = "--"; QStandardItem *BestTime = new QStandardItem(); /* QString ra, dec; if(obj->name() == "star" ) { ra = obj->ra0().toHMSString(); dec = obj->dec0().toDMSString(); BestTime->setData( QString( "--" ), Qt::DisplayRole ); } else {*/ BestTime->setData(TimeHash.value(finalObjectName, obj->transitTime(dt, geo)), Qt::DisplayRole); alt = p.alt().toDMSString(); az = p.az().toDMSString(); //} // TODO: Change the rest of the parameters to their appropriate datatypes. populateItemList(); itemList << getItemWithUserRole(KSUtils::constNameToAbbrev( KStarsData::Instance()->skyComposite()->constellationBoundary()->constellationName(obj.data()))) << BestTime << getItemWithUserRole(alt) << getItemWithUserRole(az); m_SessionModel->appendRow(itemList); //Adding an object should trigger the modified flag isModified = true; ui->SessionView->resizeColumnsToContents(); //Note addition in statusbar KStars::Instance()->statusBar()->showMessage(i18n("Added %1 to session list.", finalObjectName), 0); } setSaveImagesButton(); } void ObservingList::slotRemoveObject(const SkyObject *_o, bool session, bool update) { if (!update) // EH?! { if (!_o) _o = SkyMap::Instance()->clickedObject(); else if (sessionView) //else if is needed as clickedObject should not be removed from the session list. session = true; } // Is the pointer supplied in our own lists? const QList> &list = (session ? sessionList() : obsList()); QStandardItemModel *currentModel = (session ? m_SessionModel.get() : m_WishListModel.get()); QSharedPointer o = findObject(_o, session); if (!o) { qWarning() << "Object (name: " << getObjectName(o.data()) << ") supplied to ObservingList::slotRemoveObject() was not found in the " << QString(session ? "session" : "observing") << " list!"; return; } int k = list.indexOf(o); assert(k >= 0); // Remove from hash ImagePreviewHash.remove(o.data()); if (o.data() == LogObject) saveCurrentUserLog(); //Remove row from the TableView model // FIXME: Is there no faster way? for (int irow = 0; irow < currentModel->rowCount(); ++irow) { QString name = currentModel->item(irow, 0)->text(); if (getObjectName(o.data()) == name) { currentModel->removeRow(irow); break; } } if (!session) { obsList().removeAt(k); ui->avt->removeAllPlotObjects(); ui->WishListView->resizeColumnsToContents(); if (!update) slotSaveList(); } else { if (!update) TimeHash.remove(o->name()); sessionList().removeAt(k); //Remove from the session list isModified = true; //Removing an object should trigger the modified flag ui->avt->removeAllPlotObjects(); ui->SessionView->resizeColumnsToContents(); } } void ObservingList::slotRemoveSelectedObjects() { //Find each object by name in the session list, and remove it //Go backwards so item alignment doesn't get screwed up as rows are removed. for (int irow = getActiveModel()->rowCount() - 1; irow >= 0; --irow) { bool rowSelected; if (sessionView) rowSelected = ui->SessionView->selectionModel()->isRowSelected(irow, QModelIndex()); else rowSelected = ui->WishListView->selectionModel()->isRowSelected(irow, QModelIndex()); if (rowSelected) { QModelIndex sortIndex, index; sortIndex = getActiveSortModel()->index(irow, 0); index = getActiveSortModel()->mapToSource(sortIndex); SkyObject *o = static_cast(index.data(Qt::UserRole + 1).value()); Q_ASSERT(o); slotRemoveObject(o, sessionView); } } if (sessionView) { //we've removed all selected objects, so clear the selection ui->SessionView->selectionModel()->clear(); //Update the lists in the Execute window as well KStarsData::Instance()->executeSession()->init(); } setSaveImagesButton(); ui->ImagePreview->setCursor(Qt::ArrowCursor); } void ObservingList::slotNewSelection() { bool found = false; singleSelection = false; noSelection = false; showScope = false; //ui->ImagePreview->clearPreview(); //ui->ImagePreview->setPixmap(QPixmap()); ui->ImagePreview->setCursor(Qt::ArrowCursor); QModelIndexList selectedItems; QString newName; QSharedPointer o; QString labelText; ui->DeleteImage->setEnabled(false); selectedItems = getActiveSortModel()->mapSelectionToSource(getActiveView()->selectionModel()->selection()).indexes(); if (selectedItems.size() == getActiveModel()->columnCount()) { newName = selectedItems[0].data().toString(); singleSelection = true; //Find the selected object in the SessionList, //then break the loop. Now SessionList.current() //points to the new selected object (until now it was the previous object) for (auto& o_temp : getActiveList()) { if (getObjectName(o_temp.data()) == newName) { o = o_temp; found = true; break; } } } if (singleSelection) { //Enable buttons ui->ImagePreview->setCursor(Qt::PointingHandCursor); #ifdef HAVE_INDI showScope = true; #endif if (found) { m_CurrentObject = o.data(); //QPoint pos(0,0); plot(o.data()); //Change the m_currentImageFileName, DSS/SDSS Url to correspond to the new object setCurrentImage(o.data()); ui->SearchImage->setEnabled(true); if (newName != i18n("star")) { //Display the current object's user notes in the NotesEdit //First, save the last object's user log to disk, if necessary saveCurrentUserLog(); //uses LogObject, which is still the previous obj. //set LogObject to the new selected object LogObject = currentObject(); ui->NotesEdit->setEnabled(true); if (LogObject->userLog().isEmpty()) { ui->NotesEdit->setPlainText( i18n("Record here observation logs and/or data on %1.", getObjectName(LogObject))); } else { ui->NotesEdit->setPlainText(LogObject->userLog()); } if (sessionView) { ui->TimeEdit->setEnabled(true); ui->SetTime->setEnabled(true); ui->TimeEdit->setTime(TimeHash.value(o->name(), o->transitTime(dt, geo))); } } else //selected object is named "star" { //clear the log text box saveCurrentUserLog(); ui->NotesEdit->clear(); ui->NotesEdit->setEnabled(false); ui->SearchImage->setEnabled(false); } QString ImagePath = KSPaths::locate(QStandardPaths::GenericDataLocation, m_currentImageFileName); if (!ImagePath.isEmpty()) { //If the image is present, show it! KSDssImage ksdi(ImagePath); KSDssImage::Metadata md = ksdi.getMetadata(); //ui->ImagePreview->showPreview( QUrl::fromLocalFile( ksdi.getFileName() ) ); if (ImagePreviewHash.contains(o.data()) == false) ImagePreviewHash[o.data()] = QPixmap(ksdi.getFileName()).scaledToHeight(ui->ImagePreview->width()); //ui->ImagePreview->setPixmap(QPixmap(ksdi.getFileName()).scaledToHeight(ui->ImagePreview->width())); ui->ImagePreview->setPixmap(ImagePreviewHash[o.data()]); if (md.isValid()) { ui->dssMetadataLabel->setText( i18n("DSS Image metadata: \n Size: %1\' x %2\' \n Photometric band: %3 \n Version: %4", QString::number(md.width), QString::number(md.height), QString() + md.band, md.version)); } else ui->dssMetadataLabel->setText(i18n("No image info available.")); ui->ImagePreview->show(); ui->DeleteImage->setEnabled(true); } else { setDefaultImage(); ui->dssMetadataLabel->setText( i18n("No image available. Click on the placeholder image to download one.")); } QString cname = KStarsData::Instance()->skyComposite()->constellationBoundary()->constellationName(o.data()); if (o->type() != SkyObject::CONSTELLATION) { labelText = ""; if (o->type() == SkyObject::PLANET) labelText += o->translatedName(); else labelText += o->name(); if (std::isfinite(o->mag()) && o->mag() <= 30.) labelText += ": " + i18nc("%1 magnitude of object, %2 type of sky object (planet, asteroid " "etc), %3 name of a constellation", "%1 mag %2 in %3", o->mag(), o->typeName().toLower(), cname); else labelText += ": " + i18nc("%1 type of sky object (planet, asteroid etc), %2 name of a constellation", "%1 in %2", o->typeName(), cname); } } else { setDefaultImage(); qCWarning(KSTARS) << "Object " << newName << " not found in list."; } ui->quickInfoLabel->setText(labelText); } else { if (selectedItems.size() == 0) //Nothing selected { //Disable buttons noSelection = true; ui->NotesEdit->setEnabled(false); - m_CurrentObject = 0; + m_CurrentObject = nullptr; ui->TimeEdit->setEnabled(false); ui->SetTime->setEnabled(false); ui->SearchImage->setEnabled(false); //Clear the user log text box. saveCurrentUserLog(); ui->NotesEdit->setPlainText(""); //Clear the plot in the AVTPlotwidget ui->avt->removeAllPlotObjects(); } else //more than one object selected. { ui->NotesEdit->setEnabled(false); ui->TimeEdit->setEnabled(false); ui->SetTime->setEnabled(false); ui->SearchImage->setEnabled(false); - m_CurrentObject = 0; + m_CurrentObject = nullptr; //Clear the plot in the AVTPlotwidget ui->avt->removeAllPlotObjects(); //Clear the user log text box. saveCurrentUserLog(); ui->NotesEdit->setPlainText(""); ui->quickInfoLabel->setText(QString()); } } } void ObservingList::slotCenterObject() { if (getSelectedItems().size() == 1) { SkyMap::Instance()->setClickedObject(currentObject()); SkyMap::Instance()->setClickedPoint(currentObject()); SkyMap::Instance()->slotCenter(); } } void ObservingList::slotSlewToObject() { #ifdef HAVE_INDI if (INDIListener::Instance()->size() == 0) { KMessageBox::sorry(0, i18n("KStars did not find any active telescopes.")); return; } foreach (ISD::GDInterface *gd, INDIListener::Instance()->getDevices()) { INDI::BaseDevice *bd = gd->getBaseDevice(); if (gd->getType() != KSTARS_TELESCOPE) continue; if (bd == nullptr) continue; if (bd->isConnected() == false) { KMessageBox::error(0, i18n("Telescope %1 is offline. Please connect and retry again.", gd->getDeviceName())); return; } ISD::GDSetCommand SlewCMD(INDI_SWITCH, "ON_COORD_SET", "TRACK", ISS_ON, this); gd->setProperty(&SlewCMD); gd->runCommand(INDI_SEND_COORDS, currentObject()); return; } KMessageBox::sorry(0, i18n("KStars did not find any active telescopes.")); #endif } void ObservingList::slotAddToEkosScheduler() { #ifdef HAVE_INDI KStars::Instance()->ekosManager()->addObjectToScheduler(currentObject()); #endif } //FIXME: This will open multiple Detail windows for each object; //Should have one window whose target object changes with selection void ObservingList::slotDetails() { if (currentObject()) { QPointer dd = new DetailDialog(currentObject(), KStarsData::Instance()->ut(), geo, KStars::Instance()); dd->exec(); delete dd; } } void ObservingList::slotWUT() { KStarsDateTime lt = dt; lt.setTime(QTime(8, 0, 0)); QPointer w = new WUTDialog(KStars::Instance(), sessionView, geo, lt); w->exec(); delete w; } void ObservingList::slotAddToSession() { Q_ASSERT(!sessionView); if (getSelectedItems().size()) { foreach (const QModelIndex &i, getSelectedItems()) { foreach (QSharedPointer o, obsList()) if (getObjectName(o.data()) == i.data().toString()) slotAddObject( o.data(), true); // FIXME: Would be good to have a wrapper that accepts QSharedPointer } } } void ObservingList::slotFind() { QPointer fd = new FindDialog(KStars::Instance()); if (fd->exec() == QDialog::Accepted) { SkyObject *o = fd->targetObject(); - if (o != 0) + if (o != nullptr) { slotAddObject(o, sessionView); } } delete fd; } void ObservingList::slotEyepieceView() { KStars::Instance()->slotEyepieceView(currentObject(), getCurrentImagePath()); } void ObservingList::slotAVT() { QModelIndexList selectedItems; // TODO: Think and see if there's a more effecient way to do this. I can't seem to think of any, but this code looks like it could be improved. - Akarsh selectedItems = (sessionView ? m_SessionSortModel->mapSelectionToSource(ui->SessionView->selectionModel()->selection()).indexes() : m_WishListSortModel->mapSelectionToSource(ui->WishListView->selectionModel()->selection()).indexes()); if (selectedItems.size()) { QPointer avt = new AltVsTime(KStars::Instance()); foreach (const QModelIndex &i, selectedItems) { if (i.column() == 0) { SkyObject *o = static_cast(i.data(Qt::UserRole + 1).value()); Q_ASSERT(o); avt->processObject(o); } } avt->exec(); delete avt; } } //FIXME: On close, we will need to close any open Details/AVT windows void ObservingList::slotClose() { //Save the current User log text saveCurrentUserLog(); ui->avt->removeAllPlotObjects(); slotNewSelection(); saveCurrentList(); hide(); } void ObservingList::saveCurrentUserLog() { if (LogObject && !ui->NotesEdit->toPlainText().isEmpty() && ui->NotesEdit->toPlainText() != i18n("Record here observation logs and/or data on %1.", getObjectName(LogObject))) { LogObject->saveUserLog(ui->NotesEdit->toPlainText()); ui->NotesEdit->clear(); LogObject = nullptr; } } void ObservingList::slotOpenList() { QUrl fileURL = QFileDialog::getOpenFileUrl(KStars::Instance(), i18n("Open Observing List"), QUrl(), "KStars Observing List (*.obslist)"); QFile f; if (fileURL.isValid()) { f.setFileName(fileURL.toLocalFile()); //FIXME do we still need to do this? /* if ( ! fileURL.isLocalFile() ) { //Save remote list to a temporary local file QTemporaryFile tmpfile; tmpfile.setAutoRemove(false); tmpfile.open(); m_listFileName = tmpfile.fileName(); if( KIO::NetAccess::download( fileURL, m_listFileName, this ) ) f.setFileName( m_listFileName ); } else { m_listFileName = fileURL.toLocalFile(); f.setFileName( m_listFileName ); } */ if (!f.open(QIODevice::ReadOnly)) { QString message = i18n("Could not open file %1", f.fileName()); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); return; } saveCurrentList(); //See if the current list needs to be saved before opening the new one ui->tabWidget->setCurrentIndex(1); slotChangeTab(1); sessionList().clear(); TimeHash.clear(); - m_CurrentObject = 0; + m_CurrentObject = nullptr; m_SessionModel->removeRows(0, m_SessionModel->rowCount()); //First line is the name of the list. The rest of the file is //object names, one per line. With the TimeHash value if present QTextStream istream(&f); QString input; input = istream.readAll(); OAL::Log logObject; logObject.readBegin(input); //Set the New TimeHash TimeHash = logObject.timeHash(); geo = logObject.geoLocation(); dt = logObject.dateTime(); //foreach (SkyObject *o, *(logObject.targetList())) for (auto &o : logObject.targetList()) slotAddObject(o.data(), true); //Update the location and user set times from file slotUpdate(); //Newly-opened list should not trigger isModified flag isModified = false; f.close(); } else if (!fileURL.toLocalFile().isEmpty()) { - KMessageBox::sorry(0, i18n("The specified file is invalid")); + KMessageBox::sorry(nullptr, i18n("The specified file is invalid")); } } void ObservingList::slotClearList() { if ((ui->tabWidget->currentIndex() == 0 && obsList().isEmpty()) || (ui->tabWidget->currentIndex() == 1 && sessionList().isEmpty())) return; QString message = i18n("Are you sure you want to clear all objects?"); if (KMessageBox::questionYesNo(this, message, i18n("Clear all?")) == KMessageBox::Yes) { // Did I forget anything else to remove? ui->avt->removeAllPlotObjects(); m_CurrentObject = LogObject = nullptr; if (ui->tabWidget->currentIndex() == 0) { // IMPORTANT: Is this enough or we will have dangling pointers in memory? ImagePreviewHash.clear(); obsList().clear(); m_WishListModel->setRowCount(0); } else { // IMPORTANT: Is this enough or we will have dangling pointers in memory? sessionList().clear(); TimeHash.clear(); isModified = true; //Removing an object should trigger the modified flag m_SessionModel->setRowCount(0); } } } void ObservingList::saveCurrentList() { //Before loading a new list, do we need to save the current one? //Assume that if the list is empty, then there's no need to save if (sessionList().size()) { if (isModified) { QString message = i18n("Do you want to save the current session?"); if (KMessageBox::questionYesNo(this, message, i18n("Save Current session?"), KStandardGuiItem::save(), KStandardGuiItem::discard()) == KMessageBox::Yes) slotSaveSession(); } } } void ObservingList::slotSaveSessionAs(bool nativeSave) { if (sessionList().isEmpty()) return; QUrl fileURL = QFileDialog::getSaveFileUrl(KStars::Instance(), i18n("Save Observing List"), QUrl(), "KStars Observing List (*.obslist)"); if (fileURL.isValid()) { m_listFileName = fileURL.toLocalFile(); slotSaveSession(nativeSave); } } void ObservingList::slotSaveList() { QFile f; // FIXME: Move wishlist into a database. // TODO: Support multiple wishlists. QString fileContents; QTextStream ostream( &fileContents); // We first write to a QString to prevent truncating the file in case there is a crash. foreach (const QSharedPointer o, obsList()) { if (!o) { qWarning() << "Null entry in observing wishlist! Skipping!"; continue; } if (o->name() == "star") { //ostream << o->name() << " " << o->ra0().Hours() << " " << o->dec0().Degrees() << endl; ostream << getObjectName(o.data(), false) << endl; } else if (o->type() == SkyObject::STAR) { Q_ASSERT(dynamic_cast(o.data())); const QSharedPointer s = qSharedPointerCast(o); if (s->name() == s->gname()) ostream << s->name2() << endl; else ostream << s->name() << endl; } else { ostream << o->name() << endl; } } f.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "wishlist.obslist"); if (!f.open(QIODevice::WriteOnly)) { qWarning() << "Cannot save wish list to file!"; // TODO: This should be presented as a message box to the user return; } QTextStream writeemall(&f); writeemall << fileContents; f.close(); } void ObservingList::slotLoadWishList() { QFile f; f.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "wishlist.obslist"); if (!f.open(QIODevice::ReadOnly)) { qWarning(KSTARS) << "No WishList Saved yet"; return; } QTextStream istream(&f); QString line; QPointer addingObjectsProgress = new QProgressDialog(); addingObjectsProgress->setWindowTitle(i18n("Observing List Wizard")); addingObjectsProgress->setLabelText(i18n("Please wait while loading objects...")); addingObjectsProgress->setMaximum(0); addingObjectsProgress->setMinimum(0); addingObjectsProgress->show(); while (!istream.atEnd()) { line = istream.readLine(); //If the object is named "star", add it by coordinates SkyObject *o; /*if ( line.startsWith( QLatin1String( "star" ) ) ) { QStringList fields = line.split( ' ', QString::SkipEmptyParts ); dms ra = dms::fromString( fields[1], false ); //false = hours dms dc = dms::fromString( fields[2], true ); //true = degrees SkyPoint p( ra, dc ); double maxrad = 1000.0/Options::zoomFactor(); o = ks->data()->skyComposite()->starNearest( &p, maxrad ); } else {*/ o = KStarsData::Instance()->objectNamed(line); //} //If we haven't identified the object, try interpreting the //name as a star's genetive name (with ascii letters) if (!o) o = KStarsData::Instance()->skyComposite()->findStarByGenetiveName(line); if (o) slotAddObject(o, false, true); if (addingObjectsProgress->wasCanceled()) break; qApp->processEvents(); } delete (addingObjectsProgress); f.close(); } void ObservingList::slotSaveSession(bool nativeSave) { if (sessionList().isEmpty()) { - KMessageBox::error(0, i18n("Cannot save an empty session list!")); + KMessageBox::error(nullptr, i18n("Cannot save an empty session list!")); return; } if (m_listFileName.isEmpty()) { slotSaveSessionAs(nativeSave); return; } QFile f(m_listFileName); if (!f.open(QIODevice::WriteOnly)) { QString message = i18n("Could not open file %1. Try a different filename?", f.fileName()); - if (KMessageBox::warningYesNo(0, message, i18n("Could Not Open File"), KGuiItem(i18n("Try Different")), + if (KMessageBox::warningYesNo(nullptr, message, i18n("Could Not Open File"), KGuiItem(i18n("Try Different")), KGuiItem(i18n("Do Not Try"))) == KMessageBox::Yes) { m_listFileName.clear(); slotSaveSessionAs(nativeSave); } return; } QTextStream ostream(&f); OAL::Log log; ostream << log.writeLog(nativeSave); f.close(); isModified = false; //We've saved the session, so reset the modified flag. } void ObservingList::slotWizard() { QPointer wizard = new ObsListWizard(KStars::Instance()); if (wizard->exec() == QDialog::Accepted) { QPointer addingObjectsProgress = new QProgressDialog(); addingObjectsProgress->setWindowTitle(i18n("Observing List Wizard")); addingObjectsProgress->setLabelText(i18n("Please wait while adding objects...")); addingObjectsProgress->setMaximum(wizard->obsList().size()); addingObjectsProgress->setMinimum(0); addingObjectsProgress->setValue(0); addingObjectsProgress->show(); int counter = 1; foreach (SkyObject *o, wizard->obsList()) { slotAddObject(o); addingObjectsProgress->setValue(counter++); if (addingObjectsProgress->wasCanceled()) break; qApp->processEvents(); } delete addingObjectsProgress; } delete wizard; } void ObservingList::plot(SkyObject *o) { if (!o) return; float DayOffset = 0; if (TimeHash.value(o->name(), o->transitTime(dt, geo)).hour() > 12) DayOffset = 1; QDateTime midnight = QDateTime(dt.date(), QTime()); KStarsDateTime ut = geo->LTtoUT(KStarsDateTime(midnight)); double h1 = geo->GSTtoLST(ut.gst()).Hours(); if (h1 > 12.0) h1 -= 24.0; ui->avt->setSecondaryLimits(h1, h1 + 24.0, -90.0, 90.0); ksal->setLocation(geo); ksal->setDate(&ut); ui->avt->setGeoLocation(geo); ui->avt->setSunRiseSetTimes(ksal->getSunRise(), ksal->getSunSet()); ui->avt->setDawnDuskTimes(ksal->getDawnAstronomicalTwilight(), ksal->getDuskAstronomicalTwilight()); ui->avt->setMinMaxSunAlt(ksal->getSunMinAlt(), ksal->getSunMaxAlt()); ui->avt->setMoonRiseSetTimes(ksal->getMoonRise(), ksal->getMoonSet()); ui->avt->setMoonIllum(ksal->getMoonIllum()); ui->avt->update(); KPlotObject *po = new KPlotObject(Qt::white, KPlotObject::Lines, 2.0); for (double h = -12.0; h <= 12.0; h += 0.5) { po->addPoint(h, findAltitude(o, (h + DayOffset * 24.0))); } ui->avt->removeAllPlotObjects(); ui->avt->addPlotObject(po); } double ObservingList::findAltitude(SkyPoint *p, double hour) { // Jasem 2015-09-05 Using correct procedure to find altitude SkyPoint sp = *p; // make a copy QDateTime midnight = QDateTime(dt.date(), QTime()); KStarsDateTime ut = geo->LTtoUT(KStarsDateTime(midnight)); KStarsDateTime targetDateTime = ut.addSecs(hour * 3600.0); dms LST = geo->GSTtoLST(targetDateTime.gst()); sp.EquatorialToHorizontal(&LST, geo->lat()); return sp.alt().Degrees(); } void ObservingList::slotChangeTab(int index) { noSelection = true; saveCurrentUserLog(); ui->NotesEdit->setEnabled(false); ui->TimeEdit->setEnabled(false); ui->SetTime->setEnabled(false); ui->SearchImage->setEnabled(false); ui->DeleteImage->setEnabled(false); - m_CurrentObject = 0; + m_CurrentObject = nullptr; sessionView = index != 0; setSaveImagesButton(); ui->WizardButton->setEnabled(!sessionView); //wizard adds only to the Wish List ui->OALExport->setEnabled(sessionView); //Clear the selection in the Tables ui->WishListView->clearSelection(); ui->SessionView->clearSelection(); //Clear the user log text box. saveCurrentUserLog(); ui->NotesEdit->setPlainText(""); ui->avt->removeAllPlotObjects(); } void ObservingList::slotLocation() { QPointer ld = new LocationDialog(this); if (ld->exec() == QDialog::Accepted) { geo = ld->selectedCity(); ui->SetLocation->setText(geo->fullName()); } delete ld; } void ObservingList::slotUpdate() { dt.setDate(ui->DateEdit->date()); ui->avt->removeAllPlotObjects(); //Creating a copy of the lists, we can't use the original lists as they'll keep getting modified as the loop iterates QList> _obsList = m_WishList, _SessionList = m_SessionList; for (QSharedPointer &o : _obsList) { if (o->name() != "star") { slotRemoveObject(o.data(), false, true); slotAddObject(o.data(), false, true); } } for (QSharedPointer &obj : _SessionList) { if (obj->name() != "star") { slotRemoveObject(obj.data(), true, true); slotAddObject(obj.data(), true, true); } } } void ObservingList::slotSetTime() { SkyObject *o = currentObject(); slotRemoveObject(o, true); TimeHash[o->name()] = ui->TimeEdit->time(); slotAddObject(o, true, true); } void ObservingList::slotCustomDSS() { ui->SearchImage->setEnabled(false); //ui->ImagePreview->clearPreview(); ui->ImagePreview->setPixmap(QPixmap()); KSDssImage::Metadata md; bool ok = true; int width = QInputDialog::getInt(this, i18n("Customized DSS Download"), i18n("Specify image width (arcminutes): "), 15, 15, 75, 1, &ok); int height = QInputDialog::getInt(this, i18n("Customized DSS Download"), i18n("Specify image height (arcminutes): "), 15, 15, 75, 1, &ok); QStringList strList = (QStringList() << "poss2ukstu_blue" << "poss2ukstu_red" << "poss2ukstu_ir" << "poss1_blue" << "poss1_red" << "quickv" << "all"); QString version = QInputDialog::getItem(this, i18n("Customized DSS Download"), i18n("Specify version: "), strList, 0, false, &ok); QUrl srcUrl(KSDssDownloader::getDSSURL(currentObject()->ra0(), currentObject()->dec0(), width, height, "gif", version, &md)); delete m_dl; m_dl = new KSDssDownloader(); connect(m_dl, SIGNAL(downloadComplete(bool)), SLOT(downloadReady(bool))); m_dl->startSingleDownload(srcUrl, getCurrentImagePath(), md); } void ObservingList::slotGetImage(bool _dss, const SkyObject *o) { dss = _dss; Q_ASSERT( !o || o == currentObject()); // FIXME: Meaningless to operate on m_currentImageFileName unless o == currentObject()! if (!o) o = currentObject(); ui->SearchImage->setEnabled(false); //ui->ImagePreview->clearPreview(); //ui->ImagePreview->setPixmap(QPixmap()); setCurrentImage(o); QString currentImagePath = getCurrentImagePath(); if (QFile::exists(currentImagePath)) QFile::remove(currentImagePath); //QUrl url; dss = true; qWarning() << "FIXME: Removed support for SDSS. Until reintroduction, we will supply a DSS image"; std::function slot = std::bind(&ObservingList::downloadReady, this, std::placeholders::_1); new KSDssDownloader(o, currentImagePath, slot, this); } void ObservingList::downloadReady(bool success) { // set downloadJob to 0, but don't delete it - the job will be deleted automatically // downloadJob = 0; delete m_dl; - m_dl = 0; // required if we came from slotCustomDSS; does nothing otherwise + m_dl = nullptr; // required if we came from slotCustomDSS; does nothing otherwise if (!success) { - KMessageBox::sorry(0, i18n("Failed to download DSS/SDSS image!")); + KMessageBox::sorry(nullptr, i18n("Failed to download DSS/SDSS image!")); } else { /* if( QFile( KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + m_currentImageFileName ).size() > 13000) //The default image is around 8689 bytes */ //ui->ImagePreview->showPreview( QUrl::fromLocalFile( getCurrentImagePath() ) ); ui->ImagePreview->setPixmap(QPixmap(getCurrentImagePath()).scaledToHeight(ui->ImagePreview->width())); saveThumbImage(); ui->ImagePreview->show(); ui->ImagePreview->setCursor(Qt::PointingHandCursor); ui->DeleteImage->setEnabled(true); } /* // FIXME: Implement a priority order SDSS > DSS in the DSS downloader else if( ! dss ) slotGetImage( true ); */ } void ObservingList::setCurrentImage(const SkyObject *o) { QString sanitizedName = o->name().remove(' ').remove('\'').remove('\"').toLower(); // JM: Always use .png across all platforms. No JPGs at all? m_currentImageFileName = "image-" + sanitizedName + ".png"; m_currentThumbImageFileName = "thumb-" + sanitizedName + ".png"; // Does full image exists in the path? QString currentImagePath = KSPaths::locate(QStandardPaths::GenericDataLocation, m_currentImageFileName); // Let's try to fallback to thumb-* images if they exist if (currentImagePath.isEmpty()) { currentImagePath = KSPaths::locate(QStandardPaths::GenericDataLocation, m_currentThumbImageFileName); // If thumb image exists, let's use it if (currentImagePath.isEmpty() == false) m_currentImageFileName = m_currentThumbImageFileName; } // 2017-04-14: Unnamed stars already unsupported in observing list /* if( o->name() == "star" ) { QString RAString( o->ra0().toHMSString() ); QString DecString( o->dec0().toDMSString() ); m_currentImageFileName = "Image_J" + RAString.remove(' ').remove( ':' ) + DecString.remove(' ').remove( ':' ); // Note: Changed naming convention to standard 2016-08-25 asimha; old images shall have to be re-downloaded. // Unnecessary complication below: // QChar decsgn = ( (o->dec0().Degrees() < 0.0 ) ? '-' : '+' ); // m_currentImageFileName = m_currentImageFileName.remove('+').remove('-') + decsgn; } */ // 2017-04-14 JM: If we use .png always, let us use it across all platforms. /* QString imagePath = getCurrentImagePath(); if ( QFile::exists( imagePath)) // New convention -- append filename extension so file is usable on Windows etc. { QFile::rename( imagePath, imagePath + ".png" ); } m_currentImageFileName += ".png"; */ } QString ObservingList::getCurrentImagePath() { QString currentImagePath = KSPaths::locate(QStandardPaths::GenericDataLocation, m_currentImageFileName); if (QFile::exists(currentImagePath)) { return currentImagePath; } else return (KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + m_currentImageFileName); } void ObservingList::slotSaveAllImages() { ui->SearchImage->setEnabled(false); ui->DeleteImage->setEnabled(false); - m_CurrentObject = 0; + m_CurrentObject = nullptr; //Clear the selection in the Tables ui->WishListView->clearSelection(); ui->SessionView->clearSelection(); foreach (QSharedPointer o, getActiveList()) { if (!o) continue; // FIXME: Why would we have null objects? But appears that we do. setCurrentImage(o.data()); QString img(getCurrentImagePath()); // QUrl url( ( Options::obsListPreferDSS() ) ? DSSUrl : SDSSUrl ); // FIXME: We have removed SDSS support! QUrl url(KSDssDownloader::getDSSURL(o.data())); if (!o->isSolarSystem()) //TODO find a way for adding support for solar system images saveImage(url, img, o.data()); } } void ObservingList::saveImage(QUrl /*url*/, QString /*filename*/, const SkyObject *o) { if (!o) o = currentObject(); Q_ASSERT(o); if (!QFile::exists(getCurrentImagePath())) { // Call the DSS downloader slotGetImage(true, o); } } void ObservingList::slotImageViewer() { QPointer iv; QString currentImagePath = getCurrentImagePath(); if (QFile::exists(currentImagePath)) { QUrl url = QUrl::fromLocalFile(currentImagePath); iv = new ImageViewer(url); } if (iv) iv->show(); } void ObservingList::slotDeleteAllImages() { - if (KMessageBox::warningYesNo(0, i18n("This will delete all saved images. Are you sure you want to do this?"), + if (KMessageBox::warningYesNo(nullptr, i18n("This will delete all saved images. Are you sure you want to do this?"), i18n("Delete All Images")) == KMessageBox::No) return; ui->ImagePreview->setCursor(Qt::ArrowCursor); ui->SearchImage->setEnabled(false); ui->DeleteImage->setEnabled(false); - m_CurrentObject = 0; + m_CurrentObject = nullptr; //Clear the selection in the Tables ui->WishListView->clearSelection(); ui->SessionView->clearSelection(); //ui->ImagePreview->clearPreview(); ui->ImagePreview->setPixmap(QPixmap()); QDirIterator iterator(KSPaths::writableLocation(QStandardPaths::GenericDataLocation)); while (iterator.hasNext()) { // TODO: Probably, there should be a different directory for cached images in the observing list. if (iterator.fileName().contains("Image") && (!iterator.fileName().contains("dat")) && (!iterator.fileName().contains("obslist"))) { QFile file(iterator.filePath()); file.remove(); } iterator.next(); } } void ObservingList::setSaveImagesButton() { ui->saveImages->setEnabled(!getActiveList().isEmpty()); } // FIXME: Is there a reason to implement these as an event filter, // instead of as a signal-slot connection? Shouldn't we just use slots // to subscribe to various events from the Table / Session view? // // NOTE: ui->ImagePreview is a QLabel, which has no clicked() event or // public mouseReleaseEvent(), so eventFilter makes sense. bool ObservingList::eventFilter(QObject *obj, QEvent *event) { if (obj == ui->ImagePreview) { if (event->type() == QEvent::MouseButtonRelease) { if (currentObject()) { if (!QFile::exists(getCurrentImagePath())) { if (!currentObject()->isSolarSystem()) slotGetImage(Options::obsListPreferDSS()); else slotSearchImage(); } else slotImageViewer(); } return true; } } if (obj == ui->WishListView->viewport() || obj == ui->SessionView->viewport()) { bool sessionViewEvent = (obj == ui->SessionView->viewport()); if (event->type() == QEvent::MouseButtonRelease) // Mouse button release event { QMouseEvent *mouseEvent = static_cast(event); QPoint pos(mouseEvent->globalX(), mouseEvent->globalY()); if (mouseEvent->button() == Qt::RightButton) { if (!noSelection) { pmenu->initPopupMenu(sessionViewEvent, !singleSelection, showScope); pmenu->popup(pos); } return true; } } } if (obj == ui->WishListView || obj == ui->SessionView) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Delete) { slotRemoveSelectedObjects(); return true; } } } return false; } void ObservingList::slotSearchImage() { QPixmap *pm = new QPixmap(":/images/noimage.png"); QPointer tp = new ThumbnailPicker(currentObject(), *pm, this, 200, 200, i18n("Image Chooser")); if (tp->exec() == QDialog::Accepted) { QString currentImagePath = getCurrentImagePath(); QFile f(currentImagePath); //If a real image was set, save it. if (tp->imageFound()) { tp->image()->save(f.fileName(), "PNG"); //ui->ImagePreview->showPreview( QUrl::fromLocalFile( f.fileName() ) ); ui->ImagePreview->setPixmap(QPixmap(f.fileName()).scaledToHeight(ui->ImagePreview->width())); saveThumbImage(); slotNewSelection(); } } delete pm; delete tp; } void ObservingList::slotDeleteCurrentImage() { QFile::remove(getCurrentImagePath()); ImagePreviewHash.remove(m_CurrentObject); slotNewSelection(); } void ObservingList::saveThumbImage() { if (!QFile::exists(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + m_currentThumbImageFileName)) { QImage img(getCurrentImagePath()); img = img.scaled(200, 200, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); img.save(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + m_currentThumbImageFileName); } } QString ObservingList::getTime(const SkyObject *o) const { return TimeHash.value(o->name(), QTime(30, 0, 0)).toString("h:mm:ss AP"); } QTime ObservingList::scheduledTime(SkyObject *o) const { return TimeHash.value(o->name(), o->transitTime(dt, geo)); } void ObservingList::setTime(const SkyObject *o, QTime t) { TimeHash.insert(o->name(), t); } void ObservingList::slotOALExport() { slotSaveSessionAs(false); } void ObservingList::slotAddVisibleObj() { KStarsDateTime lt = dt; lt.setTime(QTime(8, 0, 0)); QPointer w = new WUTDialog(KStars::Instance(), sessionView, geo, lt); w->init(); QModelIndexList selectedItems; selectedItems = m_WishListSortModel->mapSelectionToSource(ui->WishListView->selectionModel()->selection()).indexes(); if (selectedItems.size()) { foreach (const QModelIndex &i, selectedItems) { foreach (QSharedPointer o, obsList()) if (getObjectName(o.data()) == i.data().toString() && w->checkVisibility(o.data())) slotAddObject( o.data(), true); // FIXME: Better if there is a QSharedPointer override for this, although the check will ensure that we don't duplicate. } } delete w; } SkyObject *ObservingList::findObjectByName(QString name) { foreach (QSharedPointer o, sessionList()) { if (getObjectName(o.data(), false) == name) return o.data(); } return nullptr; } void ObservingList::selectObject(const SkyObject *o) { ui->tabWidget->setCurrentIndex(1); ui->SessionView->selectionModel()->clear(); for (int irow = m_SessionModel->rowCount() - 1; irow >= 0; --irow) { QModelIndex mSortIndex = m_SessionSortModel->index(irow, 0); QModelIndex mIndex = m_SessionSortModel->mapToSource(mSortIndex); int idxrow = mIndex.row(); if (m_SessionModel->item(idxrow, 0)->text() == getObjectName(o)) ui->SessionView->selectRow(idxrow); slotNewSelection(); } } void ObservingList::setDefaultImage() { ui->ImagePreview->setPixmap(m_NoImagePixmap); ui->ImagePreview->update(); } QString ObservingList::getObjectName(const SkyObject *o, bool translated) { QString finalObjectName; if (o->name() == "star") { StarObject *s = (StarObject *)o; // JM: Enable HD Index stars to be added to the observing list. if (s->getHDIndex() != 0) finalObjectName = QString("HD %1").arg(QString::number(s->getHDIndex())); } else finalObjectName = translated ? o->translatedName() : o->name(); return finalObjectName; } void ObservingList::slotUpdateAltitudes() { // FIXME: Update upon gaining visibility, do not update when not visible KStarsDateTime now = KStarsDateTime::currentDateTimeUtc(); // qCDebug(KSTARS) << "Updating altitudes in observation planner @ JD - J2000 = " << double( now.djd() - J2000 ); for (int irow = m_WishListModel->rowCount() - 1; irow >= 0; --irow) { QModelIndex idx = m_WishListSortModel->mapToSource(m_WishListSortModel->index(irow, 0)); SkyObject *o = static_cast(idx.data(Qt::UserRole + 1).value()); Q_ASSERT(o); SkyPoint p = o->recomputeHorizontalCoords(now, geo); idx = m_WishListSortModel->mapToSource(m_WishListSortModel->index(irow, m_WishListSortModel->columnCount() - 1)); QStandardItem *replacement = m_altCostHelper(p); m_WishListModel->setData(idx, replacement->data(Qt::DisplayRole), Qt::DisplayRole); m_WishListModel->setData(idx, replacement->data(Qt::UserRole), Qt::UserRole); delete replacement; } emit m_WishListModel->dataChanged( m_WishListModel->index(0, m_WishListModel->columnCount() - 1), m_WishListModel->index(m_WishListModel->rowCount() - 1, m_WishListModel->columnCount() - 1)); } QSharedPointer ObservingList::findObject(const SkyObject *o, bool session) { const QList> &list = (session ? sessionList() : obsList()); const QString &target = getObjectName(o); foreach (QSharedPointer obj, list) { if (getObjectName(obj.data()) == target) return obj; } return QSharedPointer(); // null pointer } diff --git a/kstars/tools/observinglist.h b/kstars/tools/observinglist.h index 4e634b2ae..87f30ef0d 100644 --- a/kstars/tools/observinglist.h +++ b/kstars/tools/observinglist.h @@ -1,428 +1,428 @@ /*************************************************************************** observinglist.h - K Desktop Planetarium ------------------- begin : 29 Nov 2004 copyright : (C) 2004 by Jeff Woods, Jason Harris email : jcwoods@bellsouth.net, 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 "kstarsdatetime.h" #include "ui_observinglist.h" #include #include #include #include #include #include #include #include class QSortFilterProxyModel; class QStandardItem; class QStandardItemModel; class QTimer; class GeoLocation; class KSAlmanac; class KSDssDownloader; class KStars; class KStarsDateTime; class ObsListPopupMenu; class SkyObject; class SkyPoint; class ObservingListUI : public QFrame, public Ui::ObservingList { Q_OBJECT public: /** @short Cunstructor */ explicit ObservingListUI(QWidget *parent); }; /** @class ObservingList *Tool window for managing a custom list of objects. The window *displays the Name, RA, Dec, mag, and type of each object in the list. * *By selecting an object in the list, you can perform a number of functions *on that object: *+ Center it in the display *+ Examine its Details Window *+ Point the telescope at it *+ Attach a custom icon or name label (TBD) *+ Attach a trail (solar system only) (TBD) *+ Open the AltVsTime tool * *The user can also save/load their observing lists, and can export *list data (TBD: as HTML table? CSV format? plain text?) * *The observing notes associated with the selected object are displayed *below the list. * *TODO: *+ Implement a "shaded" state, in which the UI is compressed to * make it easier to float on the KStars window. Displays only * object names, and single-letter action buttons, and no user log. *+ Implement an InfoBox version (the ultimate shaded state) * *@short Tool for managing a custom list of objects *@author Jeff Woods, Jason Harris *@version 1.0 */ class ObservingList : public QDialog { Q_OBJECT public: /** @short Constructor */ ObservingList(); /** @short Destuctor (empty) */ ~ObservingList() override; /** @return reference to the current observing list */ QList> &obsList() { return m_WishList; } /** @return reference to the current observing list */ QList> &sessionList() { return m_SessionList; } /** @return pointer to the currently-selected object in the observing list *@note if more than one object is selected, this function returns 0. */ inline SkyObject *currentObject() const { return m_CurrentObject; } /** @short If the current list has unsaved changes, ask the user about saving it. *@note also clears the list in preparation of opening a new one */ void saveCurrentList(); /** @short Plot the SkyObject's Altitude vs Time in the AVTPlotWidget. *@p o pointer to the object to be plotted */ void plot(SkyObject *o); /** @short Return the altitude of the given SkyObject for the given hour. *@p p pointer to the SkyObject *@p hour time at which altitude has to be found */ double findAltitude(SkyPoint *p, double hour = 0); /** @short Sets the image parameters for the current object *@p o The passed object for setting the parameters */ void setCurrentImage(const SkyObject *o); /** * @short Returns a path to the current image, or a writable image. */ QString getCurrentImagePath(); /** @short Save the user log text to a file. *@note the log is attached to the current object in obsList. */ void saveCurrentUserLog(); /** @short decides on whether to enable the SaveImages button or not */ void setSaveImagesButton(); /** @short This is the declaration of the event filter function * which is installed on the KImageFilePreview and the TabeView */ bool eventFilter(QObject *obj, QEvent *event) override; /** @short saves a thumbnail image for the details dialog * from the downloaded image */ void saveThumbImage(); QString getTime(const SkyObject *o) const; QTime scheduledTime(SkyObject *o) const; void setTime(const SkyObject *o, QTime t); inline GeoLocation *geoLocation() { return geo; } inline KStarsDateTime dateTime() const { return dt; } /** @short return the object with the name as the passed * QString from the Session List, return null otherwise */ SkyObject *findObjectByName(QString name); /** @short make a selection in the session view */ void selectObject(const SkyObject *o); /** @short set the default image in the image preview. */ void setDefaultImage(); /** @short get object name. If star has no name, generate a name based on catalog number. * @param translated set to true if the translated name is required. */ QString getObjectName(const SkyObject *o, bool translated = true); /** * @return true if the selected list contains the given object */ inline bool contains(const SkyObject *o, bool session = false) { return bool(findObject(o, session)); } QSharedPointer findObject(const SkyObject *o, bool session = false); public slots: /** @short add a new object to list *@p o pointer to the object to add to the list *@p session flag toggle adding the object to the session list *@p update flag to toggle the call of slotSaveList */ void slotAddObject(const SkyObject *o = nullptr, bool session = false, bool update = false); /** @short Remove skyobjects which are highlighted in the *observing list tool from the observing list. */ void slotRemoveSelectedObjects(); /** @short Remove skyobject from the observing list. *@p o pointer to the SkyObject to be removed. *@p session flag to tell it whether to remove the object *from the sessionlist or from the wishlist *@p update flag to toggle the call of slotSaveList *Use SkyMap::clickedObject() if o is nullptr (default) */ void slotRemoveObject(const SkyObject *o = nullptr, bool session = false, bool update = false); /** @short center the selected object in the display */ void slotCenterObject(); /** @short slew the telescope to the selected object */ void slotSlewToObject(); /** * @brief slotAddToEkosScheduler Add object to Ekos scheduler */ void slotAddToEkosScheduler(); /** @short Show the details window for the selected object */ void slotDetails(); /** @short Show the Altitude vs Time for selecteld objects */ void slotAVT(); /** @short Open the WUT dialog */ void slotWUT(); /** @short Add the object to the Session List */ void slotAddToSession(); /** @short Open the Find Dialog */ void slotFind(); /** @short Tasks needed when changing the selected object *Save the user log of the previous selected object, *find the new selected object in the obsList, and *show the notes associated with the new selected object */ void slotNewSelection(); /** @short load an observing list from disk. */ void slotOpenList(); /** @short save the current observing list to disk. */ void slotSaveList(); /** @short Load the Wish list from disk. */ void slotLoadWishList(); /** @short save the current observing session plan to disk, specify filename. */ void slotSaveSessionAs(bool nativeSave = true); /** @short save the current session */ void slotSaveSession(bool nativeSave = true); /** @short construct a new observing list using the wizard. */ void slotWizard(); /** @short toggle the setEnabled flags according to current view *set the m_currentItem to nullptr and clear selections *@p index captures the integer value sent by the signal *which is the currentIndex of the table */ void slotChangeTab(int index); /** @short Opens the Location dialog to set the GeoLocation *for the sessionlist. */ void slotLocation(); /** @short Updates the tableviews for the new geolocation and date */ void slotUpdate(); /** @short Takes the time from the QTimeEdit box and sets it as the *time parameter in the tableview of the SessionList. */ void slotSetTime(); /** @short Downloads the corresponding DSS or SDSS image from the web and *displays it */ - void slotGetImage(bool _dss = false, const SkyObject *o = 0); + void slotGetImage(bool _dss = false, const SkyObject *o = nullptr); void slotSearchImage(); /** @short Downloads the images of all the objects in the session list *Note: This downloads the SDSS image, checks if the size is > default image *and gets the DSS image if thats the case */ void slotSaveAllImages(); /** @short saves the image syncronously from a given URL into a given file *@p url the url from which the image has to be downloaded *@p filename the file onto which the url has to be copied to *NOTE: This is not a generic image saver, it is specific to the current object */ - void saveImage(QUrl url, QString filename, const SkyObject *o = 0); + void saveImage(QUrl url, QString filename, const SkyObject *o = nullptr); /** @short Shows the image in a ImageViewer window. */ void slotImageViewer(); /** @short Remove the current image */ void slotDeleteCurrentImage(); /** @short Removes all the save DSS/SDSS images from the disk. */ void slotDeleteAllImages(); /** @short download the DSS image and show it */ void slotDSS() { slotGetImage(true); } /** *@short Present the user with options to get the right DSS image for the job */ void slotCustomDSS(); /** @short Export a target list to the oal compliant format */ void slotOALExport(); void slotAddVisibleObj(); /** * @short Show the eyepiece field view */ void slotEyepieceView(); /** * @short Recalculate and update the values of the altitude in the wishlist for the current time */ void slotUpdateAltitudes(); /** * @brief slotClearList Remove all objects from current list */ void slotClearList(); protected slots: void slotClose(); void downloadReady(bool success); protected: void showEvent(QShowEvent *) override; private: /** * @short Return the active list * @return The session list or the wish list depending on which tab is currently being viewed. */ inline QList> &getActiveList() { return ((sessionView) ? m_SessionList : m_WishList); } /** * @short Return the active itemmodel * @return the session model or the wishlist model depending on which tab is currently being viewed. */ inline QStandardItemModel *getActiveModel() const { return (sessionView ? m_SessionModel.get() : m_WishListModel.get()); } /** * @short Return the active sort model * @return the session sort model or the wishlist sort model depending on which tab is currently being viewed. */ inline QSortFilterProxyModel *getActiveSortModel() const { return (sessionView ? m_SessionSortModel.get() : m_WishListSortModel.get()); } /** * @short Return the active view * @return the active view in the UI -- session view or wishlist view depending on which one is active. */ inline QTableView *getActiveView() const { return ((sessionView) ? (ui->SessionView) : (ui->WishListView)); } /** * @short Get the currently selected item indexes * @return a QModelIndexList containing the selected rows in the active QTableView */ inline QModelIndexList getSelectedItems() const { return getActiveView()->selectionModel()->selectedRows(); } std::unique_ptr ksal; ObservingListUI *ui { nullptr }; QList> m_WishList, m_SessionList; SkyObject *LogObject { nullptr }; SkyObject *m_CurrentObject { nullptr }; bool isModified { false }; bool sessionView { false }; bool dss { false }; bool singleSelection { false }; bool showScope { false }; bool noSelection { false }; QString m_listFileName, m_currentImageFileName, m_currentThumbImageFileName; KStarsDateTime dt; GeoLocation *geo { nullptr }; std::unique_ptr m_WishListModel; std::unique_ptr m_SessionModel; std::unique_ptr m_WishListSortModel; std::unique_ptr m_SessionSortModel; QHash TimeHash; std::unique_ptr pmenu; KSDssDownloader *m_dl { nullptr }; QHash ImagePreviewHash; QPixmap m_NoImagePixmap; QTimer *m_altitudeUpdater { nullptr }; std::function m_altCostHelper; bool m_initialWishlistLoad { false }; }; diff --git a/kstars/tools/obslistpopupmenu.cpp b/kstars/tools/obslistpopupmenu.cpp index ffe902920..d199d61ca 100644 --- a/kstars/tools/obslistpopupmenu.cpp +++ b/kstars/tools/obslistpopupmenu.cpp @@ -1,95 +1,95 @@ /*************************************************************************** obslistpopupmenu.h - K Desktop Planetarium ------------------- begin : Sun July 5 2009 copyright : (C) 2008 by Prakash Mohan email : prakash.mohan@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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 "obslistpopupmenu.h" #include "config-kstars.h" #include "kstars.h" #include "kstarsdata.h" #include "observinglist.h" -ObsListPopupMenu::ObsListPopupMenu() : QMenu(0) +ObsListPopupMenu::ObsListPopupMenu() : QMenu(nullptr) { } ObsListPopupMenu::~ObsListPopupMenu() { } void ObsListPopupMenu::initPopupMenu(bool sessionView, bool multiSelection, bool showScope) { KStarsData *ksdata = KStarsData::Instance(); clear(); //Insert item for adding the object to the session view if (!sessionView) { addAction(i18n("Add to session plan"), ksdata->observingList(), SLOT(slotAddToSession())); addAction(i18n("Add objects visible tonight to session plan"), ksdata->observingList(), SLOT(slotAddVisibleObj())); #ifdef HAVE_INDI addAction(i18n("Add to Ekos Scheduler"), ksdata->observingList(), SLOT(slotAddToEkosScheduler())); #endif } addSeparator(); if (!multiSelection) addAction(i18n("Center"), ksdata->observingList(), SLOT(slotCenterObject())); //Insert item for centering on object if (!multiSelection && showScope) // Insert item for slewing telescope addAction(i18nc("Show the selected object in the telescope", "Scope"), ksdata->observingList(), SLOT(slotSlewToObject())); addSeparator(); if (!multiSelection) { addAction(i18nc("Show Detailed Information Dialog", "Details"), ksdata->observingList(), SLOT(slotDetails())); // Insert item for showing details dialog addAction(i18n("Eyepiece view"), ksdata->observingList(), SLOT(slotEyepieceView())); // Insert item for showing eyepiece view } //Insert item for opening the Altitude vs time dialog addAction(i18n("Altitude vs. Time"), ksdata->observingList(), SLOT(slotAVT())); addSeparator(); //Insert item for dowloading different images if (!multiSelection) { if (ksdata->observingList()->currentObject() != nullptr && !ksdata->observingList()->currentObject()->isSolarSystem()) { addAction(i18n("Show SDSS image"), ksdata->observingList(), SLOT(slotGetImage())); addAction(i18n("Show DSS image"), ksdata->observingList(), SLOT(slotDSS())); addAction(i18n("Customized DSS download"), ksdata->observingList(), SLOT(slotCustomDSS())); } addAction(i18n("Show images from web "), ksdata->observingList(), SLOT(slotSearchImage())); addSeparator(); } //Insert item for Removing the object(s) if (!sessionView) addAction(i18n("Remove from WishList"), ksdata->observingList(), SLOT(slotRemoveSelectedObjects())); else addAction(i18n("Remove from Session Plan"), ksdata->observingList(), SLOT(slotRemoveSelectedObjects())); } diff --git a/kstars/tools/planetviewer.cpp b/kstars/tools/planetviewer.cpp index b428637f9..084549130 100644 --- a/kstars/tools/planetviewer.cpp +++ b/kstars/tools/planetviewer.cpp @@ -1,286 +1,286 @@ /*************************************************************************** planetviewer.cpp - Display overhead view of the solar system ------------------- begin : Sun May 25 2003 copyright : (C) 2003 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 "planetviewer.h" #include "ksfilereader.h" #include "ksnumbers.h" #include "kstarsdata.h" #include "ksutils.h" #include "skyobjects/ksplanet.h" #include "skyobjects/ksplanetbase.h" #include "widgets/timespinbox.h" #include #include #include #include #include #include #include #include #include PlanetViewerUI::PlanetViewerUI(QWidget *p) : QFrame(p) { setupUi(this); } PlanetViewer::PlanetViewer(QWidget *parent) : QDialog(parent), scale(1.0), isClockRunning(false), tmr(this) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif KStarsData *data = KStarsData::Instance(); pw = new PlanetViewerUI(this); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(pw); setLayout(mainLayout); setWindowTitle(i18n("Solar System Viewer")); //setMainWidget( pw ); //setButtons( QDialog::Close ); setModal(false); pw->map->setLimits(-48.0, 48.0, -48.0, 48.0); pw->map->axis(KPlotWidget::BottomAxis) ->setLabel(i18nc("axis label for x-coordinate of solar system viewer. AU means astronomical unit.", "X-position (AU)")); pw->map->axis(KPlotWidget::LeftAxis) ->setLabel(i18nc("axis label for y-coordinate of solar system viewer. AU means astronomical unit.", "Y-position (AU)")); pw->TimeStep->setDaysOnly(true); pw->TimeStep->tsbox()->setValue(1); //start with 1-day timestep pw->RunButton->setIcon(QIcon::fromTheme("arrow-right", QIcon(":/icons/breeze/default/arrow-right.svg"))); pw->ZoomInButton->setIcon(QIcon::fromTheme("zoom-in", QIcon(":/icons/breeze/default/zoom-in.svg"))); pw->ZoomOutButton->setIcon(QIcon::fromTheme("zoom-out", QIcon(":/icons/breeze/default/zoom-out.svg"))); pw->DateBox->setDate(data->lt().date()); resize(500, 500); pw->map->QWidget::setFocus(); //give keyboard focus to the plot widget for key and mouse events setCenterPlanet(QString()); PlanetList.append(KSPlanetBase::createPlanet(KSPlanetBase::MERCURY)); PlanetList.append(KSPlanetBase::createPlanet(KSPlanetBase::VENUS)); PlanetList.append(new KSPlanet("Earth")); PlanetList.append(KSPlanetBase::createPlanet(KSPlanetBase::MARS)); PlanetList.append(KSPlanetBase::createPlanet(KSPlanetBase::JUPITER)); PlanetList.append(KSPlanetBase::createPlanet(KSPlanetBase::SATURN)); PlanetList.append(KSPlanetBase::createPlanet(KSPlanetBase::URANUS)); PlanetList.append(KSPlanetBase::createPlanet(KSPlanetBase::NEPTUNE)); //PlanetList.append( KSPlanetBase::createPlanet( KSPlanetBase::PLUTO ) ); ut = data->ut(); KSNumbers num(ut.djd()); for (int i = 0; i < PlanetList.count(); ++i) { - PlanetList[i]->findPosition(&num, 0, 0); // nullptr args: don't need geocent. coords. + PlanetList[i]->findPosition(&num, nullptr, nullptr); // nullptr args: don't need geocent. coords. LastUpdate[i] = int(ut.date().toJulianDay()); } //The planets' update intervals are 0.25% of one period: UpdateInterval[0] = 0; UpdateInterval[1] = 0; UpdateInterval[2] = 0; UpdateInterval[3] = 1; UpdateInterval[4] = 5; UpdateInterval[5] = 13; UpdateInterval[6] = 38; UpdateInterval[7] = 75; //UpdateInterval[8] = 113; QTimer::singleShot(0, this, SLOT(initPlotObjects())); connect(&tmr, SIGNAL(timeout()), SLOT(tick())); connect(pw->TimeStep, SIGNAL(scaleChanged(float)), SLOT(setTimeScale(float))); connect(pw->RunButton, SIGNAL(clicked()), SLOT(slotRunClock())); connect(pw->ZoomInButton, SIGNAL(clicked()), pw->map, SLOT(slotZoomIn())); connect(pw->ZoomOutButton, SIGNAL(clicked()), pw->map, SLOT(slotZoomOut())); connect(pw->DateBox, SIGNAL(dateChanged(QDate)), SLOT(slotChangeDate())); connect(pw->TodayButton, SIGNAL(clicked()), SLOT(slotToday())); connect(this, SIGNAL(closeClicked()), SLOT(slotCloseWindow())); } QString PlanetViewer::planetName(uint i) const { return PlanetList[i]->name(); } void PlanetViewer::tick() { //Update the time/date ut.setDJD(ut.djd() + scale * 0.1); pw->DateBox->setDate(ut.date()); updatePlanets(); } void PlanetViewer::setTimeScale(float f) { scale = f / 86400.; //convert seconds to days } void PlanetViewer::slotRunClock() { isClockRunning = !isClockRunning; if (isClockRunning) { pw->RunButton->setIcon( QIcon::fromTheme("media-playback-pause", QIcon(":/icons/breeze/default/media-playback-pause.svg"))); tmr.start(100); // pw->DateBox->setEnabled( false ); } else { pw->RunButton->setIcon(QIcon::fromTheme("arrow-right", QIcon(":/icons/breeze/default/arrow-right.svg"))); tmr.stop(); // pw->DateBox->setEnabled( true ); } } void PlanetViewer::slotChangeDate() { ut.setDate(pw->DateBox->date()); updatePlanets(); } void PlanetViewer::slotCloseWindow() { //Stop the clock if it's running if (isClockRunning) { tmr.stop(); isClockRunning = false; pw->RunButton->setIcon(QIcon::fromTheme("arrow-right", QIcon(":/icons/breeze/default/arrow-right.svg"))); } } void PlanetViewer::updatePlanets() { KSNumbers num(ut.djd()); bool changed(false); //Check each planet to see if it needs to be updated for (int i = 0; i < PlanetList.count(); ++i) { if (abs(int(ut.date().toJulianDay()) - LastUpdate[i]) > UpdateInterval[i]) { KSPlanetBase *p = PlanetList[i]; p->findPosition(&num); double s, c, s2, c2; p->helEcLong().SinCos(s, c); p->helEcLat().SinCos(s2, c2); QList points = planet[i]->points(); points.at(0)->setX(p->rsun() * c * c2); points.at(0)->setY(p->rsun() * s * c2); if (centerPlanet() == p->name()) { QRectF dataRect = pw->map->dataRect(); double xc = (dataRect.right() + dataRect.left()) * 0.5; double yc = (dataRect.bottom() + dataRect.top()) * 0.5; double dx = points.at(0)->x() - xc; double dy = points.at(0)->y() - yc; pw->map->setLimits(dataRect.x() + dx, dataRect.right() + dx, dataRect.y() + dy, dataRect.bottom() + dy); } LastUpdate[i] = int(ut.date().toJulianDay()); changed = true; } } if (changed) pw->map->update(); } void PlanetViewer::slotToday() { pw->DateBox->setDate(KStarsData::Instance()->lt().date()); } void PlanetViewer::paintEvent(QPaintEvent *) { pw->map->update(); } void PlanetViewer::initPlotObjects() { // Planets ksun = new KPlotObject(Qt::yellow, KPlotObject::Points, 12, KPlotObject::Circle); ksun->addPoint(0.0, 0.0); pw->map->addPlotObject(ksun); //Read in the orbit curves for (int i = 0; i < PlanetList.count(); ++i) { KSPlanetBase *p = PlanetList[i]; KPlotObject *orbit = new KPlotObject(Qt::white, KPlotObject::Lines, 1.0); QFile orbitFile; QString orbitFileName = (p->isMajorPlanet() ? ((KSPlanet *)p)->untranslatedName().toLower() : p->name().toLower()) + ".orbit"; if (KSUtils::openDataFile(orbitFile, orbitFileName)) { KSFileReader fileReader(orbitFile); // close file is included double x, y; while (fileReader.hasMoreLines()) { QString line = fileReader.readLine(); QStringList fields = line.split(' ', QString::SkipEmptyParts); if (fields.size() == 3) { x = fields[0].toDouble(); y = fields[1].toDouble(); orbit->addPoint(x, y); } } } pw->map->addPlotObject(orbit); } for (int i = 0; i < PlanetList.count(); ++i) { KSPlanetBase *p = PlanetList[i]; planet[i] = new KPlotObject(p->color(), KPlotObject::Points, 6, KPlotObject::Circle); double s, c; p->helEcLong().SinCos(s, c); planet[i]->addPoint(p->rsun() * c, p->rsun() * s, p->translatedName()); pw->map->addPlotObject(planet[i]); } update(); } void PlanetViewer::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) close(); else e->ignore(); } diff --git a/kstars/tools/scriptbuilder.cpp b/kstars/tools/scriptbuilder.cpp index 214545c26..dc73c3c50 100644 --- a/kstars/tools/scriptbuilder.cpp +++ b/kstars/tools/scriptbuilder.cpp @@ -1,2877 +1,2877 @@ /*************************************************************************** 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 "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); #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif } 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); } OptionsTreeView::~OptionsTreeView() { } 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); int w = qfm.width(child->text(icol)) + 4; 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]) << endl; 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 ks = (KStars *)parent; 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 (int i = 0; i < KStarsFunctionList.size(); ++i) new QTreeWidgetItem(kstars_tree, QStringList(KStarsFunctionList[i]->prototype())); for (int i = 0; i < SimClockFunctionList.size(); ++i) new QTreeWidgetItem(simclock_tree, QStringList(SimClockFunctionList[i]->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", QIcon(":/icons/breeze/default/document-new.svg"))); sb->OpenButton->setIcon(QIcon::fromTheme("document-open", QIcon(":/icons/breeze/default/document-open.svg"))); sb->SaveButton->setIcon(QIcon::fromTheme("document-save", QIcon(":/icons/breeze/default/document-save.svg"))); sb->SaveAsButton->setIcon( QIcon::fromTheme("document-save-as", QIcon(":/icons/breeze/default/document-save-as.svg"))); sb->RunButton->setIcon(QIcon::fromTheme("system-run", QIcon(":/icons/breeze/default/system-run.svg"))); sb->CopyButton->setIcon(QIcon::fromTheme("view-refresh", QIcon(":/icons/breeze/default/view-refresh.svg"))); sb->AddButton->setIcon(QIcon::fromTheme("go-previous", QIcon(":/icons/breeze/default/go-previous.svg"))); sb->RemoveButton->setIcon(QIcon::fromTheme("go-next", QIcon(":/icons/breeze/default/go-next.svg"))); sb->UpButton->setIcon(QIcon::fromTheme("go-up", QIcon(":/icons/breeze/default/go-up.svg"))); sb->DownButton->setIcon(QIcon::fromTheme("go-down", QIcon(":/icons/breeze/default/go-down.svg"))); 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(ks); otv = new OptionsTreeView(ks); 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 < ks->data()->colorScheme()->numberOfColors(); ++i) { argSetColor->ColorName->addItem(ks->data()->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 ) ) - KMessageBox::sorry(0, i18n("Could not download remote file."), i18n("Download Error")); + KMessageBox::sorry(nullptr, i18n("Could not download remote file."), i18n("Download Error")); } QFile f(fname); if (!f.open(QIODevice::ReadOnly)) { QString message = i18n("Could not open file %1.", f.fileName()); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, message, i18n("Could Not Open File")); currentFileURL.clear(); return; } QTextStream istream(&f); readScript(istream); f.close(); } else if (!currentFileURL.url().isEmpty()) { QString message = i18n("Invalid URL: %1", currentFileURL.url()); - KMessageBox::sorry(0, message, i18n("Invalid URL")); + KMessageBox::sorry(nullptr, message, 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()); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, 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()); - KMessageBox::sorry(0, message, i18n("Could not upload file")); + KMessageBox::sorry(nullptr, message, i18n("Could not upload file")); } } setUnsavedChanges(false); } else { QString message = i18n("Invalid URL: %1", currentFileURL.url()); - KMessageBox::sorry(0, message, i18n("Invalid URL")); + KMessageBox::sorry(nullptr, 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(0, message, caption, KStandardGuiItem::save(), KStandardGuiItem::discard()); + 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()); - KMessageBox::sorry(0, message, i18n("Could Not Open File")); + KMessageBox::sorry(nullptr, 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 :( */ 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" << endl; ostream << "#KStars DBus script: " << currentScriptName << endl; ostream << "#by " << currentAuthor << endl; ostream << "#last modified: " << KStarsDateTime::currentDateTime().toString(Qt::ISODate) << endl; ostream << "#" << endl; foreach (ScriptFunction *sf, ScriptList) { if (!sf->valid()) continue; if (sf->isClockFunction()) { ostream << dbus_call << clock_method << sf->scriptLine() << endl; } else { ostream << dbus_call << main_method << sf->scriptLine() << endl; } } //Write script footer ostream << "##" << endl; } 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(0); + 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(ks->map()->width()); argExportImage->ExportHeight->setValue(ks->map()->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(ks->data()->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() { QPointer fd = new FindDialog(ks); if (fd->exec() == QDialog::Accepted && fd->targetObject()) { setUnsavedChanges(true); if (sender() == argLookToward->FindButton) argLookToward->FocusEdit->setEditText(fd->targetObject()->name()); else argFindObject->NameEdit->setText(fd->targetObject()->name()); } delete fd; } 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 switcing 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 switcing 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 switcing 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 switcing 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(ks->data()->colorScheme()->colorAt(argSetColor->ColorName->currentIndex())); sf->setArg(0, ks->data()->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, ks->data()->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 switcing 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 switcing 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 switcing 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 switcing 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/starhopperdialog.cpp b/kstars/tools/starhopperdialog.cpp index bc13a5e48..0908bd3e8 100644 --- a/kstars/tools/starhopperdialog.cpp +++ b/kstars/tools/starhopperdialog.cpp @@ -1,169 +1,169 @@ /*************************************************************************** starhopperdialog.cpp - UI of Star Hopping Guide for KStars ------------------- begin : Sat 15th Nov 2014 copyright : (C) 2014 Utkarsh Simha email : utkarshsimha@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. * * * ***************************************************************************/ #include "starhopperdialog.h" #include "kstars.h" #include "ksutils.h" #include "skymap.h" #include "skymapcomposite.h" #include "starhopper.h" #include "targetlistcomponent.h" #include "dialogs/detaildialog.h" StarHopperDialog::StarHopperDialog(QWidget *parent) : QDialog(parent), ui(new Ui::StarHopperDialog) { ui->setupUi(this); m_lw = ui->listWidget; m_Metadata = new QStringList(); ui->directionsLabel->setWordWrap(true); m_sh.reset(new StarHopper()); connect(ui->NextButton, SIGNAL(clicked()), this, SLOT(slotNext())); connect(ui->GotoButton, SIGNAL(clicked()), this, SLOT(slotGoto())); connect(ui->DetailsButton, SIGNAL(clicked()), this, SLOT(slotDetails())); connect(m_lw, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotGoto())); connect(m_lw, SIGNAL(itemSelectionChanged()), this, SLOT(slotRefreshMetadata())); connect(this, SIGNAL(finished(int)), this, SLOT(deleteLater())); } StarHopperDialog::~StarHopperDialog() { TargetListComponent *t = getTargetListComponent(); if (t->list) t->list->clear(); SkyMap::Instance()->forceUpdate(true); } void StarHopperDialog::starHop(const SkyPoint &startHop, const SkyPoint &stopHop, float fov, float maglim) { QList *starList = m_sh->computePath(startHop, stopHop, fov, maglim, m_Metadata); if (!starList->empty()) { foreach (StarObject *so, *starList) { setData(so); } slotRefreshMetadata(); m_skyObjList = KSUtils::castStarObjListToSkyObjList(starList); starList->clear(); delete starList; TargetListComponent *t = getTargetListComponent(); t->list.reset(m_skyObjList); SkyMap::Instance()->forceUpdate(true); } else { delete starList; KMessageBox::error(this, i18n("Star-hopper algorithm failed. If you're trying a large star hop, try using a " "smaller FOV or changing the source point")); } } void StarHopperDialog::setData(StarObject *sobj) { QListWidgetItem *item = new QListWidgetItem(); QString starName; if (sobj->name() != "star") { starName = sobj->translatedLongName(); } else if (sobj->getHDIndex()) { starName = QString("HD%1").arg(QString::number(sobj->getHDIndex())); } else { starName = ""; starName += sobj->spchar(); starName += QString(" Star of mag %2").arg(QString::number(sobj->mag())); } item->setText(starName); QVariant qv; qv.setValue(sobj); item->setData(Qt::UserRole, qv); m_lw->addItem(item); } void StarHopperDialog::slotNext() { m_lw->setCurrentRow(m_lw->currentRow() + 1); slotGoto(); } void StarHopperDialog::slotGoto() { slotRefreshMetadata(); SkyObject *skyobj = getStarData(m_lw->currentItem()); if (skyobj != nullptr) { KStars *ks = KStars::Instance(); ks->map()->setClickedObject(skyobj); ks->map()->setClickedPoint(skyobj); ks->map()->slotCenter(); } } void StarHopperDialog::slotDetails() { SkyObject *skyobj = getStarData(m_lw->currentItem()); if (skyobj != nullptr) { DetailDialog *detailDialog = new DetailDialog(skyobj, KStarsData::Instance()->ut(), KStarsData::Instance()->geo(), KStars::Instance()); detailDialog->exec(); delete detailDialog; } } SkyObject *StarHopperDialog::getStarData(QListWidgetItem *item) { if (!item) - return 0; + return nullptr; else { QVariant v = item->data(Qt::UserRole); StarObject *starobj = v.value(); return starobj; } } inline TargetListComponent *StarHopperDialog::getTargetListComponent() { return KStarsData::Instance()->skyComposite()->getStarHopRouteList(); } void StarHopperDialog::slotRefreshMetadata() { int row = m_lw->currentRow(); qDebug() << "Slot RefreshMetadata"; if (row >= 0) { ui->directionsLabel->setText(m_Metadata->at(row)); } else { ui->directionsLabel->setText(m_Metadata->at(0)); } qDebug() << "Slot RefreshMetadata"; } diff --git a/kstars/tools/whatsinteresting/skyobjlistmodel.cpp b/kstars/tools/whatsinteresting/skyobjlistmodel.cpp index ba4c82516..4e57081f7 100644 --- a/kstars/tools/whatsinteresting/skyobjlistmodel.cpp +++ b/kstars/tools/whatsinteresting/skyobjlistmodel.cpp @@ -1,92 +1,92 @@ /*************************************************************************** skyobjlistmodel.cpp - K Desktop Planetarium ------------------- begin : 2012/26/05 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. * * * ***************************************************************************/ #include "skyobjlistmodel.h" #include "skyobject.h" #include "skyobjitem.h" SkyObjListModel::SkyObjListModel(SkyObjItem *soitem, QObject *parent) : QAbstractListModel(parent) { //FIXME Needs porting to KF5 //Fixed in roleNames(). setRoleNames is not a member of QAbstractListModel anymore //setRoleNames(soitem->roleNames()); Q_UNUSED(soitem); } QHash SkyObjListModel::roleNames() const { QHash roles; roles[SkyObjItem::DispNameRole] = "dispName"; roles[SkyObjItem::DispImageRole] = "imageSource"; roles[SkyObjItem::DispSummaryRole] = "dispObjSummary"; roles[SkyObjItem::CategoryRole] = "type"; roles[SkyObjItem::CategoryNameRole] = "typeName"; return roles; } void SkyObjListModel::addSkyObject(SkyObjItem *soitem) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_SoItemList.append(soitem); endInsertRows(); } int SkyObjListModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) return m_SoItemList.size(); } QVariant SkyObjListModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() > rowCount()) return QVariant(); SkyObjItem *soitem = m_SoItemList[index.row()]; return soitem->data(role); } QList SkyObjListModel::getSkyObjItems() { return m_SoItemList; } SkyObjItem *SkyObjListModel::getSkyObjItem(int index) { if (m_SoItemList.size() > index) return m_SoItemList[index]; else - return 0; + return nullptr; } int SkyObjListModel::getSkyObjIndex(SkyObjItem *item) { for (int i = 0; i < m_SoItemList.size(); i++) { if (item->getName() == m_SoItemList[i]->getName()) return i; } return -1; } void SkyObjListModel::resetModel() { m_SoItemList.clear(); } diff --git a/kstars/tools/whatsinteresting/wiview.cpp b/kstars/tools/whatsinteresting/wiview.cpp index d5b2f9dee..f24cf4bcf 100644 --- a/kstars/tools/whatsinteresting/wiview.cpp +++ b/kstars/tools/whatsinteresting/wiview.cpp @@ -1,1019 +1,1019 @@ /*************************************************************************** wiview.cpp - K Desktop Planetarium ------------------- begin : 2012/26/05 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. * * * ***************************************************************************/ #include "wiview.h" #include "kspaths.h" #include "kstars.h" #include "modelmanager.h" #include "obsconditions.h" #include "Options.h" #include "skymap.h" #include "skymapcomposite.h" #include "skyobjitem.h" #include "skyobjlistmodel.h" #include "starobject.h" #include "wiequipsettings.h" #include "dialogs/detaildialog.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_INDI #include #include "indi/indilistener.h" #endif WIView::WIView(QWidget *parent) : QWidget(parent) { //These settings are like this just to get it started. int bortle = Options::bortleClass(); int aperture = 100; ObsConditions::Equipment equip = ObsConditions::Telescope; ObsConditions::TelescopeType telType = ObsConditions::Reflector; m_Obs = new ObsConditions(bortle, aperture, equip, telType); m_ModManager.reset(new ModelManager(m_Obs)); m_BaseView = new QQuickView(); ///To use i18n() instead of qsTr() in qml/wiview.qml for translation //KDeclarative kd; // kd.setDeclarativeEngine(m_BaseView->engine()); //kd.initialize(); //kd.setupBindings(); m_Ctxt = m_BaseView->rootContext(); m_Ctxt->setContextProperty("soListModel", m_ModManager->getTempModel()); // This is to avoid an error saying it doesn't exist. ///Use instead of KDeclarative m_Ctxt->setContextObject(new KLocalizedContext(m_BaseView)); QString WI_Location; #if defined(Q_OS_OSX) WI_Location = QCoreApplication::applicationDirPath() + "/../Resources/data/tools/whatsinteresting/qml/wiview.qml"; if (!QFileInfo(WI_Location).exists()) WI_Location = KSPaths::locate(QStandardPaths::AppDataLocation, "tools/whatsinteresting/qml/wiview.qml"); #elif defined(Q_OS_WIN) WI_Location = KSPaths::locate(QStandardPaths::GenericDataLocation, "tools/whatsinteresting/qml/wiview.qml"); #else WI_Location = KSPaths::locate(QStandardPaths::AppDataLocation, "tools/whatsinteresting/qml/wiview.qml"); #endif m_BaseView->setSource(QUrl::fromLocalFile(WI_Location)); m_BaseObj = m_BaseView->rootObject(); m_ProgressBar = m_BaseObj->findChild("progressBar"); m_loadingMessage = m_BaseObj->findChild("loadingMessage"); m_CategoryTitle = m_BaseObj->findChild(QString("categoryTitle")); m_ViewsRowObj = m_BaseObj->findChild(QString("viewsRowObj")); connect(m_ViewsRowObj, SIGNAL(categorySelected(QString)), this, SLOT(onCategorySelected(QString))); connect(m_ViewsRowObj, SIGNAL(inspectSkyObject(QString)), this, SLOT(inspectSkyObject(QString))); m_SoListObj = m_BaseObj->findChild("soListObj"); connect(m_SoListObj, SIGNAL(soListItemClicked(int)), this, SLOT(onSoListItemClicked(int))); m_DetailsViewObj = m_BaseObj->findChild("detailsViewObj"); descTextObj = m_DetailsViewObj->findChild("descTextObj"); infoBoxText = m_DetailsViewObj->findChild("infoBoxText"); m_NextObj = m_BaseObj->findChild("nextObj"); connect(m_NextObj, SIGNAL(nextObjClicked()), this, SLOT(onNextObjClicked())); m_PrevObj = m_BaseObj->findChild("prevObj"); connect(m_PrevObj, SIGNAL(prevObjClicked()), this, SLOT(onPrevObjClicked())); m_CenterButtonObj = m_BaseObj->findChild("centerButtonObj"); connect(m_CenterButtonObj, SIGNAL(centerButtonClicked()), this, SLOT(onCenterButtonClicked())); autoCenterCheckbox = m_DetailsViewObj->findChild("autoCenterCheckbox"); autoTrackCheckbox = m_DetailsViewObj->findChild("autoTrackCheckbox"); m_SlewTelescopeButtonObj = m_BaseObj->findChild("slewTelescopeButtonObj"); connect(m_SlewTelescopeButtonObj, SIGNAL(slewTelescopeButtonClicked()), this, SLOT(onSlewTelescopeButtonClicked())); m_DetailsButtonObj = m_BaseObj->findChild("detailsButtonObj"); connect(m_DetailsButtonObj, SIGNAL(detailsButtonClicked()), this, SLOT(onDetailsButtonClicked())); QObject *settingsIconObj = m_BaseObj->findChild("settingsIconObj"); connect(settingsIconObj, SIGNAL(settingsIconClicked()), this, SLOT(onSettingsIconClicked())); inspectIconObj = m_BaseObj->findChild("inspectIconObj"); connect(inspectIconObj, SIGNAL(inspectIconClicked(bool)), this, SLOT(onInspectIconClicked(bool))); visibleIconObj = m_BaseObj->findChild("visibleIconObj"); connect(visibleIconObj, SIGNAL(visibleIconClicked(bool)), this, SLOT(onVisibleIconClicked(bool))); favoriteIconObj = m_BaseObj->findChild("favoriteIconObj"); connect(favoriteIconObj, SIGNAL(favoriteIconClicked(bool)), this, SLOT(onFavoriteIconClicked(bool))); QObject *reloadIconObj = m_BaseObj->findChild("reloadIconObj"); connect(reloadIconObj, SIGNAL(reloadIconClicked()), this, SLOT(onReloadIconClicked())); QObject *downloadIconObj = m_BaseObj->findChild("downloadIconObj"); connect(downloadIconObj, SIGNAL(downloadIconClicked()), this, SLOT(onUpdateIconClicked())); m_BaseView->setResizeMode(QQuickView::SizeRootObjectToView); m_BaseView->show(); // Fix some weird issue with what's interesting panel view under Windows // In Qt 5.9 it content is messed up and there is no way to close the panel #ifdef Q_OS_WIN m_BaseView->setFlags(Qt::WindowCloseButtonHint); #endif connect(KStars::Instance()->map(), SIGNAL(objectClicked(SkyObject*)), this, SLOT(inspectSkyObjectOnClick(SkyObject*))); manager.reset(new QNetworkAccessManager()); setProgressBarVisible(true); connect(m_ModManager.get(), SIGNAL(loadProgressUpdated(double)), this, SLOT(updateProgress(double))); connect(m_ModManager.get(), SIGNAL(modelUpdated()), this, SLOT(refreshListView())); m_ViewsRowObj->setProperty("enabled", false); inspectOnClick = false; nightVision = m_BaseObj->findChild("nightVision"); if (Options::darkAppColors()) nightVision->setProperty("state", "active"); } WIView::~WIView() { } void WIView::setNightVisionOn(bool on) { if (on) nightVision->setProperty("state", "active"); else nightVision->setProperty("state", ""); if (m_CurSoItem != nullptr) loadDetailsView(m_CurSoItem, m_CurIndex); } void WIView::setProgressBarVisible(bool visible) { m_ProgressBar->setProperty("visible", visible); } void WIView::updateProgress(double value) { m_ProgressBar->setProperty("value", value); if (value == 1) { setProgressBarVisible(false); m_ViewsRowObj->setProperty("enabled", true); m_loadingMessage->setProperty("state", ""); } else { setProgressBarVisible(true); m_loadingMessage->setProperty("state", "loading"); } } void WIView::updateObservingConditions() { int bortle = Options::bortleClass(); /** NOTE This part of the code dealing with equipment type is presently not required as WI does not differentiate between Telescope and Binoculars. It only needs the aperture of the equipment whichever available. However this is kept as a part of the code as support to be utilised in the future. **/ ObsConditions::Equipment equip = ObsConditions::None; if (Options::telescopeCheck() && Options::binocularsCheck()) equip = ObsConditions::Both; else if (Options::telescopeCheck()) equip = ObsConditions::Telescope; else if (Options::binocularsCheck()) equip = ObsConditions::Binoculars; ObsConditions::TelescopeType telType; if (KStars::Instance()->getWIEquipSettings()) telType = (equip == ObsConditions::Telescope) ? KStars::Instance()->getWIEquipSettings()->getTelType() : ObsConditions::Invalid; else telType = ObsConditions::Invalid; int aperture = 100; //This doesn't work correctly, FIXME!! // if(KStars::Instance()->getWIEquipSettings()) // aperture = KStars::Instance()->getWIEquipSettings()->getAperture(); if (!m_Obs) m_Obs = new ObsConditions(bortle, aperture, equip, telType); else m_Obs->setObsConditions(bortle, aperture, equip, telType); } void WIView::onCategorySelected(QString model) { m_CurrentObjectListName = model; m_Ctxt->setContextProperty("soListModel", m_ModManager->returnModel(m_CurrentObjectListName)); m_CurIndex = -2; if (!m_ModManager->showOnlyVisibleObjects()) visibleIconObj->setProperty("state", "unchecked"); if (!m_ModManager->showOnlyFavoriteObjects()) favoriteIconObj->setProperty("state", "unchecked"); if (model == "ngc" && (!m_ModManager->isNGCLoaded())) { QtConcurrent::run(m_ModManager.get(), &ModelManager::loadNGCCatalog); return; } if (model == "ic" && (!m_ModManager->isICLoaded())) { QtConcurrent::run(m_ModManager.get(), &ModelManager::loadICCatalog); return; } if (model == "sharpless" && (!m_ModManager->isSharplessLoaded())) { QtConcurrent::run(m_ModManager.get(), &ModelManager::loadSharplessCatalog); return; } updateModel(*m_Obs); } void WIView::onSoListItemClicked(int index) { SkyObjItem *soitem = m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjItem(index); if (soitem) loadDetailsView(soitem, index); } void WIView::onNextObjClicked() { if (!m_CurrentObjectListName.isEmpty()) { int modelSize = m_ModManager->returnModel(m_CurrentObjectListName)->rowCount(); SkyObjItem *nextItem = m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjItem((m_CurIndex + 1) % modelSize); loadDetailsView(nextItem, (m_CurIndex + 1) % modelSize); } } void WIView::onPrevObjClicked() { if (!m_CurrentObjectListName.isEmpty()) { int modelSize = m_ModManager->returnModel(m_CurrentObjectListName)->rowCount(); SkyObjItem *prevItem = m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjItem((m_CurIndex - 1 + modelSize) % modelSize); loadDetailsView(prevItem, (m_CurIndex - 1 + modelSize) % modelSize); } } void WIView::onCenterButtonClicked() { ///Center map on selected sky-object SkyObject *so = m_CurSoItem->getSkyObject(); KStars *kstars = KStars::Instance(); if (so) { kstars->map()->setFocusPoint(so); kstars->map()->setFocusObject(so); kstars->map()->setDestination(*kstars->map()->focusPoint()); Options::setIsTracking(autoTrackCheckbox->property("checked") == true); } } void WIView::onSlewTelescopeButtonClicked() { if (KMessageBox::Continue == KMessageBox::warningContinueCancel(nullptr, "Are you sure you want your telescope to slew to this object?", i18n("Continue Slew"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "continue_wi_slew_warning")) { #ifdef HAVE_INDI if (INDIListener::Instance()->size() == 0) { KMessageBox::sorry(0, i18n("KStars did not find any active telescopes.")); return; } foreach (ISD::GDInterface *gd, INDIListener::Instance()->getDevices()) { INDI::BaseDevice *bd = gd->getBaseDevice(); if (gd->getType() != KSTARS_TELESCOPE) continue; if (bd == nullptr) continue; if (bd->isConnected() == false) { KMessageBox::error( 0, i18n("Telescope %1 is offline. Please connect and retry again.", gd->getDeviceName())); return; } ISD::GDSetCommand SlewCMD(INDI_SWITCH, "ON_COORD_SET", "TRACK", ISS_ON, this); gd->setProperty(&SlewCMD); gd->runCommand(INDI_SEND_COORDS, m_CurSoItem->getSkyObject()); /// Slew map to selected sky-object onCenterButtonClicked(); return; } KMessageBox::sorry(0, i18n("KStars did not find any active telescopes.")); #endif } } void WIView::onDetailsButtonClicked() { ///Code taken from WUTDialog::slotDetails() KStars *kstars = KStars::Instance(); SkyObject *so = m_CurSoItem->getSkyObject(); if (so) { DetailDialog *detail = new DetailDialog(so, kstars->data()->lt(), kstars->data()->geo(), kstars); detail->exec(); delete detail; } } void WIView::onSettingsIconClicked() { KStars *kstars = KStars::Instance(); kstars->showWISettingsUI(); } void WIView::onReloadIconClicked() { if (!m_CurrentObjectListName.isEmpty()) { updateModel(*m_Obs); m_CurIndex = m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjIndex(m_CurSoItem); } loadDetailsView(m_CurSoItem, m_CurIndex); } void WIView::onVisibleIconClicked(bool visible) { m_ModManager->setShowOnlyVisibleObjects(visible); onReloadIconClicked(); } void WIView::onFavoriteIconClicked(bool favorites) { m_ModManager->setShowOnlyFavoriteObjects(favorites); onReloadIconClicked(); } void WIView::onUpdateIconClicked() { QMessageBox mbox; QPushButton *currentObject = mbox.addButton("Current Object", QMessageBox::AcceptRole); QPushButton *missingObjects = nullptr; QPushButton *allObjects = nullptr; mbox.setText("Please choose which object(s) to try to update with Wikipedia data."); if (!m_CurrentObjectListName.isEmpty()) { missingObjects = mbox.addButton("Objects with no data", QMessageBox::AcceptRole); allObjects = mbox.addButton("Entire List", QMessageBox::AcceptRole); } QPushButton *cancel = mbox.addButton("Cancel", QMessageBox::AcceptRole); mbox.setDefaultButton(cancel); mbox.exec(); if (mbox.clickedButton() == currentObject) { if (m_CurSoItem != nullptr) { tryToUpdateWikipediaInfo(m_CurSoItem, getWikipediaName(m_CurSoItem)); } } else if (mbox.clickedButton() == allObjects || mbox.clickedButton() == missingObjects) { SkyObjListModel *model = m_ModManager->returnModel(m_CurrentObjectListName); if (model->rowCount() > 0) { tryToUpdateWikipediaInfoInModel(mbox.clickedButton() == missingObjects); } else { qDebug() << "No Objects in List!"; } } } void WIView::refreshListView() { - m_Ctxt->setContextProperty("soListModel", 0); + m_Ctxt->setContextProperty("soListModel", nullptr); if (!m_CurrentObjectListName.isEmpty()) m_Ctxt->setContextProperty("soListModel", m_ModManager->returnModel(m_CurrentObjectListName)); if (m_CurIndex == -2) onSoListItemClicked(0); if (m_CurIndex != -1) m_SoListObj->setProperty("currentIndex", m_CurIndex); } void WIView::updateModel(ObsConditions& obs) { if (!m_CurrentObjectListName.isEmpty()) { m_Obs = &obs; m_ModManager->updateModel(m_Obs, m_CurrentObjectListName); } } void WIView::inspectSkyObject(const QString& name) { if (!name.isEmpty() && name != "star") { SkyObject *obj = KStarsData::Instance()->skyComposite()->findByName(name); if (obj) inspectSkyObject(obj); } } void WIView::inspectSkyObjectOnClick(SkyObject *obj) { if (inspectOnClick && KStars::Instance()->isWIVisible()) inspectSkyObject(obj); } void WIView::inspectSkyObject(SkyObject *obj) { if (!obj) return; if (obj->name() != "star") { m_CurrentObjectListName = ""; trackedItem.reset(new SkyObjItem(obj)); loadDetailsView(trackedItem.get(), -1); m_BaseObj->setProperty("state", "singleItemSelected"); m_CategoryTitle->setProperty("text", "Selected Object"); } } void WIView::loadDetailsView(SkyObjItem *soitem, int index) { if (soitem == nullptr) return; int modelSize = -1; if (index != -1) modelSize = m_ModManager->returnModel(m_CurrentObjectListName)->rowCount(); if (soitem != m_CurSoItem) m_CurSoItem = soitem; m_CurIndex = index; if (modelSize <= 1) { m_NextObj->setProperty("visible", "false"); m_PrevObj->setProperty("visible", "false"); } else { SkyObjItem *nextItem = m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjItem((m_CurIndex + 1) % modelSize); SkyObjItem *prevItem = m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjItem((m_CurIndex - 1 + modelSize) % modelSize); m_NextObj->setProperty("visible", "true"); m_PrevObj->setProperty("visible", "true"); QObject *nextTextObj = m_NextObj->findChild("nextTextObj"); nextTextObj->setProperty("text", nextItem->getName()); QObject *prevTextObj = m_PrevObj->findChild("prevTextObj"); prevTextObj->setProperty("text", prevItem->getName()); } QObject *sonameObj = m_DetailsViewObj->findChild("sonameObj"); QObject *posTextObj = m_DetailsViewObj->findChild("posTextObj"); QObject *detailImage = m_DetailsViewObj->findChild("detailImage"); QObject *detailsTextObj = m_DetailsViewObj->findChild("detailsTextObj"); sonameObj->setProperty("text", soitem->getDescName()); posTextObj->setProperty("text", soitem->getPosition()); detailImage->setProperty("refreshableSource", soitem->getImageURL(false)); loadObjectDescription(soitem); infoBoxText->setProperty( "text", "

No Wikipedia information.
Please try to download it using the orange download button below."); loadObjectInfoBox(soitem); QString summary = soitem->getSummary(false); QString magText; if (soitem->getType() == SkyObjItem::Constellation) magText = xi18n("Magnitude: --"); else magText = xi18n("Magnitude: %1", QLocale().toString(soitem->getMagnitude(), 'f', 2)); QString sbText = xi18n("Surface Brightness: %1", soitem->getSurfaceBrightness()); QString sizeText = xi18n("Size: %1", soitem->getSize()); QString details = summary + "
" + sbText + "
" + magText + "
" + sizeText; detailsTextObj->setProperty("text", details); if (autoCenterCheckbox->property("checked") == true) { QTimer::singleShot(500, this, SLOT(onCenterButtonClicked())); } if (m_CurIndex != -1) m_SoListObj->setProperty("currentIndex", m_CurIndex); } QString WIView::getWikipediaName(SkyObjItem *soitem) { if (!soitem) return ""; QString name; if (soitem->getName().toLower().startsWith(QLatin1String("m "))) name = soitem->getName().replace("M ", "Messier_").remove(' '); else if (soitem->getName().toLower().startsWith(QLatin1String("ngc"))) name = soitem->getName().toLower().replace("ngc", "NGC_").remove(' '); else if (soitem->getName().toLower().startsWith(QLatin1String("ic"))) name = soitem->getName().toLower().replace("ic", "IC_").remove(' '); else if (soitem->getType() == SkyObjItem::Constellation) { QStringList words = soitem->getName().split(" "); for (int i = 0; i < words.size(); i++) { QString temp = words.at(i).toLower(); temp[0] = temp[0].toUpper(); words.replace(i, temp); } name = words.join("_") + "_(constellation)"; if (name.contains("Serpens")) name = "Serpens_(constellation)"; } else if (soitem->getTypeName() == "Asteroid") name = soitem->getName().remove(' ') + "_(asteroid)"; else if (soitem->getTypeName() == "Comet") name = soitem->getLongName(); else if (soitem->getType() == SkyObjItem::Planet && soitem->getName() != "Sun" && soitem->getName() != "Moon") name = soitem->getName().remove(' ') + "_(planet)"; else if (soitem->getType() == SkyObjItem::Star) { StarObject *star = dynamic_cast(soitem->getSkyObject()); // The greek name seems to give the most consistent search results for opensearch. name = star->gname(false).replace(' ', '_'); if (name.isEmpty()) name = soitem->getName().replace(' ', '_') + "_(star)"; name.remove('[').remove(']'); } else name = soitem->getName().remove(' '); return name; } void WIView::updateWikipediaDescription(SkyObjItem *soitem) { if (!soitem) return; QString name = getWikipediaName(soitem); QUrl url("https://en.wikipedia.org/w/api.php?action=opensearch&search=" + name + "&format=xml"); QNetworkReply *response = manager->get(QNetworkRequest(url)); QTimer::singleShot(30000, response, [response] { //Shut it down after 30 sec. response->abort(); response->deleteLater(); qDebug() << "Wikipedia Download Timed out."; }); connect(response, &QNetworkReply::finished, this, [soitem, this, response] { response->deleteLater(); if (response->error() != QNetworkReply::NoError) return; QString contentType = response->header(QNetworkRequest::ContentTypeHeader).toString(); if (!contentType.contains("charset=utf-8")) { qWarning() << "Content charsets other than utf-8 are not implemented yet."; return; } QString result = QString::fromUtf8(response->readAll()); int leftPos = result.indexOf("") - leftPos; int leftURL = result.indexOf("") + 26; int rightURL = result.indexOf("") - leftURL; QString srchtml = "\n

Source: (" + "Wikipedia)"; //Note the \n is so that the description is put on another line in the file. Doesn't affect the display but allows the source to be loaded in the details but not the list. QString html = "" + result.mid(leftPos, rightPos) + srchtml + ""; saveObjectInfoBoxText(soitem, "description", html); QString color = (Options::darkAppColors()) ? "red" : "white"; QString linkColor = (Options::darkAppColors()) ? "red" : "yellow"; html = "" + html + ""; if (soitem == m_CurSoItem) descTextObj->setProperty("text", html); refreshListView(); }); } void WIView::loadObjectDescription(SkyObjItem *soitem) { QFile file; QString fname = "description-" + soitem->getName().toLower().remove(' ') + ".html"; file.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "descriptions/" + fname); //determine filename in local user KDE directory tree. if (file.exists()) { if (file.open(QIODevice::ReadOnly)) { QTextStream in(&file); QString color = (Options::darkAppColors()) ? "red" : "white"; QString linkColor = (Options::darkAppColors()) ? "red" : "yellow"; QString line = "
" + in.readAll() + ""; descTextObj->setProperty("text", line); file.close(); } } else { descTextObj->setProperty("text", soitem->getTypeName()); } } void WIView::loadObjectInfoBox(SkyObjItem *soitem) { if (!soitem) return; QFile file; QString fname = "infoText-" + soitem->getName().toLower().remove(' ') + ".html"; file.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "descriptions/" + fname); //determine filename in local user KDE directory tree. if (file.exists()) { if (file.open(QIODevice::ReadOnly)) { QTextStream in(&file); QString infoBoxHTML; while (!in.atEnd()) { infoBoxHTML = in.readAll(); QString wikiImageName = QUrl::fromLocalFile( KSPaths::locate(QStandardPaths::GenericDataLocation, "descriptions/wikiImage-" + soitem->getName().toLower().remove(' ') + ".png")) .url(); if (!wikiImageName.isEmpty()) { int captionEnd = infoBoxHTML.indexOf( ""); //Start looking for the image AFTER the caption. Planets have images in their caption. if (captionEnd == -1) captionEnd = 0; int leftImg = infoBoxHTML.indexOf("src=\"", captionEnd) + 5; int rightImg = infoBoxHTML.indexOf("\"", leftImg) - leftImg; QString imgURL = infoBoxHTML.mid(leftImg, rightImg); infoBoxHTML.replace(imgURL, wikiImageName); } QString color = (Options::darkAppColors()) ? "red" : "white"; QString linkColor = (Options::darkAppColors()) ? "red" : "yellow"; if (Options::darkAppColors()) infoBoxHTML.replace("color: white", "color: " + color); infoBoxHTML = "" + infoBoxHTML + ""; infoBoxText->setProperty("text", infoBoxHTML); } file.close(); } } } void WIView::tryToUpdateWikipediaInfoInModel(bool onlyMissing) { SkyObjListModel *model = m_ModManager->returnModel(m_CurrentObjectListName); int objectNum = model->rowCount(); for (int i = 0; i < objectNum; i++) { SkyObjItem *soitem = model->getSkyObjItem(i); QFile file; QString fname = "infoText-" + soitem->getName().toLower().remove(' ') + ".html"; file.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "descriptions/" + fname); //determine filename in local user KDE directory tree. if (file.exists() && onlyMissing) continue; tryToUpdateWikipediaInfo(soitem, getWikipediaName(soitem)); } } void WIView::tryToUpdateWikipediaInfo(SkyObjItem *soitem, QString name) { if (name.isEmpty() || !soitem) return; QUrl url("https://en.wikipedia.org/w/index.php?action=render&title=" + name + "&redirects"); QNetworkReply *response = manager->get(QNetworkRequest(url)); QTimer::singleShot(30000, response, [response] { //Shut it down after 30 sec. response->abort(); response->deleteLater(); qDebug() << "Wikipedia Download Timed out."; }); connect(response, &QNetworkReply::finished, this, [name, response, soitem, this] { response->deleteLater(); if (response->error() == QNetworkReply::ContentNotFoundError) { QString html = "
Sorry, No Wikipedia article with this object name seems to exist. It is possible that " "one does exist but does not match the namimg scheme."; saveObjectInfoBoxText(soitem, "infoText", html); infoBoxText->setProperty("text", html); return; } if (response->error() != QNetworkReply::NoError) return; QString result = QString::fromUtf8(response->readAll()); int leftPos = result.indexOf("", leftPos) - leftPos; if (leftPos == -1) { //No InfoBox is Found if (soitem->getType() == SkyObjItem::Star && name != soitem->getName().replace(' ', '_')) //For stars, the regular name rather than gname { tryToUpdateWikipediaInfo(soitem, soitem->getName().replace(' ', '_')); return; } QString html = "
Sorry, no Information Box in the object's Wikipedia article was found."; saveObjectInfoBoxText(soitem, "infoText", html); infoBoxText->setProperty("text", html); return; } updateWikipediaDescription(soitem); QString infoText = result.mid(leftPos, rightPos); //This if statement should correct for a situation like for the planets where there is a single internal table inside the infoText Box. if (infoText.indexOf("", leftPos + rightPos + 6) - leftPos; infoText = result.mid(leftPos, rightPos); } //This next section is for the headers in the colored boxes. It turns them black instead of white because they are more visible that way. infoText.replace("background: #", "color:black;background: #") .replace("background-color: #", "color:black;background: #") .replace("background:#", "color:black;background:#") .replace("background-color:#", "color:black;background:#") .replace("background: pink", "color:black;background: pink"); infoText.replace("//", "http://"); //This is to fix links on wikipedia which are missing http from the url infoText.replace("https:http:", "https:") .replace("http:http:", "http:"); //Just in case it was done to an actual complete url //This section is intended to remove links from the object name header at the top. The links break up the header. int thLeft = infoText.indexOf("
", thLeft); int firstA = infoText.indexOf("", firstA) - firstA + 1; infoText.remove(firstA, rightA); int endA = infoText.indexOf("", firstA); infoText.remove(endA, 4); } } int annotationLeft = infoText.indexOf("", annotationLeft) + 13 - annotationLeft; infoText.remove(annotationLeft, annotationRight); //This removes the annotation that does not render correctly for some DSOs. int mathLeft = infoText.indexOf("", mathLeft) + 1 - mathLeft; infoText.remove(mathLeft, mathRight); //This removes an image that doesn't render properly for some DSOs. infoText.replace("style=\"width:22em\"", "style=\"width:100%;background-color: black;color: white;\""); infoText = infoText + "
(Source: Wikipedia)"; saveInfoURL(soitem, "https://en.wikipedia.org/w/index.php?title=" + name + "&redirects"); int captionEnd = infoText.indexOf( ""); //Start looking for the image AFTER the caption. Planets have images in their caption. if (captionEnd == -1) captionEnd = 0; int leftImg = infoText.indexOf("src=\"", captionEnd) + 5; if (leftImg > captionEnd + 5) { int rightImg = infoText.indexOf("\"", leftImg) - leftImg; QString imgURL = infoText.mid(leftImg, rightImg); imgURL.replace( "http://upload.wikimedia.org", "https://upload.wikimedia.org"); //Although they will display, the images apparently don't download properly unless they are https. saveImageURL(soitem, imgURL); downloadWikipediaImage(soitem, imgURL); } QString html = "
" + infoText + "
"; saveObjectInfoBoxText(soitem, "infoText", html); QString color = (Options::darkAppColors()) ? "red" : "white"; QString linkColor = (Options::darkAppColors()) ? "red" : "yellow"; if (Options::darkAppColors()) html.replace("color: white", "color: " + color); html = "" + html + ""; if (soitem == m_CurSoItem) infoBoxText->setProperty("text", html); }); } void WIView::saveObjectInfoBoxText(SkyObjItem *soitem, QString type, QString text) { QFile file; QString fname = type + '-' + soitem->getName().toLower().remove(' ') + ".html"; QDir writableDir; QString filePath = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "descriptions"; writableDir.mkpath(filePath); file.setFileName(filePath + '/' + fname); //determine filename in local user KDE directory tree. if (file.open(QIODevice::WriteOnly) == false) { qDebug() << "Image text cannot be saved for later. file save error"; return; } else { QTextStream stream(&file); stream << text; file.close(); } } void WIView::saveImageURL(SkyObjItem *soitem, QString imageURL) { QFile file; file.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "image_url.dat"); //determine filename in local user KDE directory tree. QString entry = soitem->getName() + ':' + "Show Wikipedia Image" + ':' + imageURL; if (file.open(QIODevice::ReadOnly)) { QTextStream in(&file); QString line; while (!in.atEnd()) { line = in.readLine(); if (line == entry) { file.close(); return; } } file.close(); } if (!file.open(QIODevice::ReadWrite | QIODevice::Append)) { qDebug() << "Image URL cannot be saved for later. image_url.dat error"; return; } else { QTextStream stream(&file); stream << entry << endl; file.close(); } } void WIView::saveInfoURL(SkyObjItem *soitem, QString infoURL) { QFile file; file.setFileName(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "info_url.dat"); //determine filename in local user KDE directory tree. QString entry = soitem->getName() + ':' + "Wikipedia Page" + ':' + infoURL; if (file.open(QIODevice::ReadOnly)) { QTextStream in(&file); QString line; while (!in.atEnd()) { line = in.readLine(); if (line == entry) { file.close(); return; } } file.close(); } if (!file.open(QIODevice::ReadWrite | QIODevice::Append)) { qDebug() << "Info URL cannot be saved for later. info_url.dat error"; return; } else { QTextStream stream(&file); stream << entry << endl; file.close(); } } void WIView::downloadWikipediaImage(SkyObjItem *soitem, QString imageURL) { QString fname = "wikiImage-" + soitem->getName().toLower().remove(' ') + ".png"; QDir writableDir; QString filePath = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + "descriptions"; writableDir.mkpath(filePath); QString fileN = filePath + '/' + fname; QNetworkReply *response = manager->get(QNetworkRequest(QUrl(imageURL))); QTimer::singleShot(60000, response, [response] { //Shut it down after 60 sec. response->abort(); response->deleteLater(); qDebug() << "Image Download Timed out."; }); connect(response, &QNetworkReply::finished, this, [fileN, response, this] { response->deleteLater(); if (response->error() != QNetworkReply::NoError) return; QImage *image = new QImage(); QByteArray responseData = response->readAll(); if (image->loadFromData(responseData)) { image->save(fileN); refreshListView(); //This is to update the images displayed with the new image. } else qDebug() << "image not downloaded"; }); } diff --git a/kstars/tools/wutdialog.cpp b/kstars/tools/wutdialog.cpp index dc85bdc48..4b70f1c12 100644 --- a/kstars/tools/wutdialog.cpp +++ b/kstars/tools/wutdialog.cpp @@ -1,628 +1,628 @@ /*************************************************************************** wutdialog.cpp - K Desktop Planetarium ------------------- begin : Die Feb 25 2003 copyright : (C) 2003 by Thomas Kabelmann email : tk78@gmx.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. * * * ***************************************************************************/ #include "wutdialog.h" #include "kstars.h" #include "skymap.h" #include "dialogs/detaildialog.h" #include "dialogs/locationdialog.h" #include "dialogs/timedialog.h" #include "skyobjects/kssun.h" #include "skyobjects/ksmoon.h" #include "skycomponents/skymapcomposite.h" #include "tools/observinglist.h" WUTDialogUI::WUTDialogUI(QWidget *p) : QFrame(p) { setupUi(this); } WUTDialog::WUTDialog(QWidget *parent, bool _session, GeoLocation *_geo, KStarsDateTime _lt) : QDialog(parent), session(_session), T0(_lt), geo(_geo) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif WUT = new WUTDialogUI(this); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(WUT); setLayout(mainLayout); setWindowTitle(i18n("What's up Tonight")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); setModal(false); //If the Time is earlier than 6:00 am, assume the user wants the night of the previous date if (T0.time().hour() < 6) T0 = T0.addDays(-1); //Now, set time T0 to midnight (of the following day) T0.setTime(QTime(0, 0, 0)); T0 = T0.addDays(1); UT0 = geo->LTtoUT(T0); //Set the Tomorrow date/time to Noon the following day Tomorrow = T0.addSecs(12 * 3600); TomorrowUT = geo->LTtoUT(Tomorrow); //Set the Evening date/time to 6:00 pm Evening = T0.addSecs(-6 * 3600); EveningUT = geo->LTtoUT(Evening); QString sGeo = geo->translatedName(); if (!geo->translatedProvince().isEmpty()) sGeo += ", " + geo->translatedProvince(); sGeo += ", " + geo->translatedCountry(); WUT->LocationLabel->setText(i18n("at %1", sGeo)); WUT->DateLabel->setText(i18n("The night of %1", QLocale().toString(Evening.date(), QLocale::LongFormat))); m_Mag = 10.0; WUT->MagnitudeEdit->setValue(m_Mag); //WUT->MagnitudeEdit->setSliderEnabled( true ); WUT->MagnitudeEdit->setSingleStep(0.100); initCategories(); makeConnections(); QTimer::singleShot(0, this, SLOT(init())); } WUTDialog::~WUTDialog() { } void WUTDialog::makeConnections() { connect(WUT->DateButton, SIGNAL(clicked()), SLOT(slotChangeDate())); connect(WUT->LocationButton, SIGNAL(clicked()), SLOT(slotChangeLocation())); connect(WUT->CenterButton, SIGNAL(clicked()), SLOT(slotCenter())); connect(WUT->DetailButton, SIGNAL(clicked()), SLOT(slotDetails())); connect(WUT->ObslistButton, SIGNAL(clicked()), SLOT(slotObslist())); connect(WUT->CategoryListWidget, SIGNAL(currentTextChanged(QString)), SLOT(slotLoadList(QString))); connect(WUT->ObjectListWidget, SIGNAL(currentTextChanged(QString)), SLOT(slotDisplayObject(QString))); connect(WUT->EveningMorningBox, SIGNAL(activated(int)), SLOT(slotEveningMorning(int))); connect(WUT->MagnitudeEdit, SIGNAL(valueChanged(double)), SLOT(slotChangeMagnitude())); } void WUTDialog::initCategories() { m_Categories << i18n("Planets") << i18n("Stars") << i18n("Nebulae") << i18n("Galaxies") << i18n("Star Clusters") << i18n("Constellations") << i18n("Asteroids") << i18n("Comets"); foreach (const QString &c, m_Categories) WUT->CategoryListWidget->addItem(c); WUT->CategoryListWidget->setCurrentRow(0); } void WUTDialog::init() { QString sRise, sSet, sDuration; float Dur; int hDur, mDur; KStarsData *data = KStarsData::Instance(); // reset all lists foreach (const QString &c, m_Categories) { if (m_VisibleList.contains(c)) visibleObjects(c).clear(); else m_VisibleList[c] = QList(); m_CategoryInitialized[c] = false; } // sun almanac information KSSun *oSun = dynamic_cast(data->objectNamed("Sun")); sunRiseTomorrow = oSun->riseSetTime(TomorrowUT, geo, true); sunSetToday = oSun->riseSetTime(EveningUT, geo, false); sunRiseToday = oSun->riseSetTime(EveningUT, geo, true); //check to see if Sun is circumpolar KSNumbers *num = new KSNumbers(UT0.djd()); KSNumbers *oldNum = new KSNumbers(data->ut().djd()); CachingDms LST = geo->GSTtoLST(T0.gst()); oSun->updateCoords(num, true, geo->lat(), &LST, true); if (oSun->checkCircumpolar(geo->lat())) { if (oSun->alt().Degrees() > 0.0) { sRise = i18n("circumpolar"); sSet = i18n("circumpolar"); sDuration = "00:00"; Dur = hDur = mDur = 0; } else { sRise = i18n("does not rise"); sSet = i18n("does not rise"); sDuration = "24:00"; Dur = hDur = 24; mDur = 0; } } else { //Round times to the nearest minute by adding 30 seconds to the time sRise = QLocale().toString(sunRiseTomorrow); sSet = QLocale().toString(sunSetToday); Dur = 24.0 + (float)sunRiseTomorrow.hour() + (float)sunRiseTomorrow.minute() / 60.0 + (float)sunRiseTomorrow.second() / 3600.0 - (float)sunSetToday.hour() - (float)sunSetToday.minute() / 60.0 - (float)sunSetToday.second() / 3600.0; hDur = int(Dur); mDur = int(60.0 * (Dur - (float)hDur)); QTime tDur(hDur, mDur); sDuration = QLocale().toString(tDur); } WUT->SunSetLabel->setText(i18nc("Sunset at time %1 on date %2", "Sunset: %1 on %2", sSet, QLocale().toString(Evening.date(), QLocale::LongFormat))); WUT->SunRiseLabel->setText(i18nc("Sunrise at time %1 on date %2", "Sunrise: %1 on %2", sRise, QLocale().toString(Tomorrow.date(), QLocale::LongFormat))); if (Dur == 0) WUT->NightDurationLabel->setText(i18n("Night duration: %1", sDuration)); else if (Dur > 1) WUT->NightDurationLabel->setText(i18n("Night duration: %1 hours", sDuration)); else if (Dur == 1) WUT->NightDurationLabel->setText(i18n("Night duration: %1 hour", sDuration)); else if (mDur > 1) WUT->NightDurationLabel->setText(i18n("Night duration: %1 minutes", sDuration)); else if (mDur == 1) WUT->NightDurationLabel->setText(i18n("Night duration: %1 minute", sDuration)); // moon almanac information KSMoon *oMoon = dynamic_cast(data->objectNamed("Moon")); moonRise = oMoon->riseSetTime(UT0, geo, true); moonSet = oMoon->riseSetTime(UT0, geo, false); //check to see if Moon is circumpolar oMoon->updateCoords(num, true, geo->lat(), &LST, true); if (oMoon->checkCircumpolar(geo->lat())) { if (oMoon->alt().Degrees() > 0.0) { sRise = i18n("circumpolar"); sSet = i18n("circumpolar"); } else { sRise = i18n("does not rise"); sSet = i18n("does not rise"); } } else { //Round times to the nearest minute by adding 30 seconds to the time sRise = QLocale().toString(moonRise.addSecs(30)); sSet = QLocale().toString(moonSet.addSecs(30)); } WUT->MoonRiseLabel->setText( i18n("Moon rises at: %1 on %2", sRise, QLocale().toString(Evening.date(), QLocale::LongFormat))); // If the moon rises and sets on the same day, this will be valid [ Unless // the moon sets on the next day after staying on for over 24 hours ] if (moonSet > moonRise) WUT->MoonSetLabel->setText( i18n("Moon sets at: %1 on %2", sSet, QLocale().toString(Evening.date(), QLocale::LongFormat))); else WUT->MoonSetLabel->setText( i18n("Moon sets at: %1 on %2", sSet, QLocale().toString(Tomorrow.date(), QLocale::LongFormat))); - oMoon->findPhase(0); + oMoon->findPhase(nullptr); WUT->MoonIllumLabel->setText(oMoon->phaseName() + QString(" (%1%)").arg(int(100.0 * oMoon->illum()))); //Restore Sun's and Moon's coordinates, and recompute Moon's original Phase oMoon->updateCoords(oldNum, true, geo->lat(), data->lst(), true); oSun->updateCoords(oldNum, true, geo->lat(), data->lst(), true); - oMoon->findPhase(0); + oMoon->findPhase(nullptr); if (WUT->CategoryListWidget->currentItem()) slotLoadList(WUT->CategoryListWidget->currentItem()->text()); delete num; delete oldNum; } QList &WUTDialog::visibleObjects(const QString &category) { return m_VisibleList[category]; } bool WUTDialog::isCategoryInitialized(const QString &category) { return m_CategoryInitialized[category]; } void WUTDialog::slotLoadList(const QString &c) { KStarsData *data = KStarsData::Instance(); if (!m_VisibleList.contains(c)) return; WUT->ObjectListWidget->clear(); setCursor(QCursor(Qt::WaitCursor)); if (!isCategoryInitialized(c)) { if (c == m_Categories[0]) //Planets { foreach (const QString &name, data->skyComposite()->objectNames(SkyObject::PLANET)) { SkyObject *o = data->skyComposite()->findByName(name); if (checkVisibility(o) && o->mag() <= m_Mag) visibleObjects(c).append(o); } m_CategoryInitialized[c] = true; } else if (c == m_Categories[1]) //Stars { QVector> starObjects; starObjects.append(data->skyComposite()->objectLists(SkyObject::STAR)); starObjects.append(data->skyComposite()->objectLists(SkyObject::CATALOG_STAR)); for (QVector>::iterator it = starObjects.begin();it != starObjects.end();it++) { const SkyObject *o = it->second; if (checkVisibility(o) && o->mag() <= m_Mag) { visibleObjects(c).append(o); } } m_CategoryInitialized[c] = true; } else if (c == m_Categories[5]) //Constellations { foreach (SkyObject *o, data->skyComposite()->constellationNames()) if (checkVisibility(o)) visibleObjects(c).append(o); m_CategoryInitialized[c] = true; } else if (c == m_Categories[6]) //Asteroids { foreach (SkyObject *o, data->skyComposite()->asteroids()) if (checkVisibility(o) && o->name() != i18n("Pluto") && o->mag() <= m_Mag) visibleObjects(c).append(o); m_CategoryInitialized[c] = true; } else if (c == m_Categories[7]) //Comets { foreach (SkyObject *o, data->skyComposite()->comets()) if (checkVisibility(o) && o->mag() <= m_Mag) visibleObjects(c).append(o); m_CategoryInitialized[c] = true; } else //all deep-sky objects, need to split clusters, nebulae and galaxies { foreach (DeepSkyObject *dso, data->skyComposite()->deepSkyObjects()) { SkyObject *o = (SkyObject *)dso; if (checkVisibility(o) && o->mag() <= m_Mag) { switch (o->type()) { case SkyObject::OPEN_CLUSTER: //fall through case SkyObject::GLOBULAR_CLUSTER: visibleObjects(m_Categories[4]).append(o); //star clusters break; case SkyObject::GASEOUS_NEBULA: //fall through case SkyObject::PLANETARY_NEBULA: //fall through case SkyObject::SUPERNOVA_REMNANT: visibleObjects(m_Categories[2]).append(o); //nebulae break; case SkyObject::GALAXY: visibleObjects(m_Categories[3]).append(o); //galaxies break; } } } m_CategoryInitialized[m_Categories[2]] = true; m_CategoryInitialized[m_Categories[3]] = true; m_CategoryInitialized[m_Categories[4]] = true; } } //Now the category has been initialized, we can populate the list widget foreach (const SkyObject *o, visibleObjects(c)) WUT->ObjectListWidget->addItem(o->name()); setCursor(QCursor(Qt::ArrowCursor)); // highlight first item if (WUT->ObjectListWidget->count()) { WUT->ObjectListWidget->setCurrentRow(0); WUT->ObjectListWidget->setFocus(); } } bool WUTDialog::checkVisibility(const SkyObject *o) { bool visible(false); double minAlt = 6.0; //An object is considered 'visible' if it is above horizon during civil twilight. // reject objects that never rise if (o->checkCircumpolar(geo->lat()) == true && o->alt().Degrees() <= 0) return false; //Initial values for T1, T2 assume all night option of EveningMorningBox KStarsDateTime T1 = Evening; T1.setTime(sunSetToday); KStarsDateTime T2 = Tomorrow; T2.setTime(sunRiseTomorrow); //Check Evening/Morning only state: if (EveningFlag == 0) //Evening only { T2 = T0; //midnight } else if (EveningFlag == 1) //Morning only { T1 = T0; //midnight } for (KStarsDateTime test = T1; test < T2; test = test.addSecs(3600)) { //Need LST of the test time, expressed as a dms object. KStarsDateTime ut = geo->LTtoUT(test); dms LST = geo->GSTtoLST(ut.gst()); SkyPoint sp = o->recomputeCoords(ut, geo); //check altitude of object at this time. sp.EquatorialToHorizontal(&LST, geo->lat()); if (sp.alt().Degrees() > minAlt) { visible = true; break; } } return visible; } void WUTDialog::slotDisplayObject(const QString &name) { QTime tRise, tSet, tTransit; QString sRise, sTransit, sSet; sRise = "--:--"; sTransit = "--:--"; sSet = "--:--"; WUT->DetailButton->setEnabled(false); - SkyObject *o = 0; + SkyObject *o = nullptr; if (name.isEmpty()) { //no object selected WUT->ObjectBox->setTitle(i18n("No Object Selected")); - o = 0; + o = nullptr; } else { o = KStarsData::Instance()->objectNamed(name); if (!o) //should never get here { WUT->ObjectBox->setTitle(i18n("Object Not Found")); } } if (o) { WUT->ObjectBox->setTitle(o->name()); if (o->checkCircumpolar(geo->lat())) { if (o->alt().Degrees() > 0.0) { sRise = i18n("circumpolar"); sSet = i18n("circumpolar"); } else { sRise = i18n("does not rise"); sSet = i18n("does not rise"); } } else { tRise = o->riseSetTime(T0, geo, true); tSet = o->riseSetTime(T0, geo, false); // if ( tSet < tRise ) // tSet = o->riseSetTime( JDTomorrow, geo, false ); sRise.clear(); sRise.sprintf("%02d:%02d", tRise.hour(), tRise.minute()); sSet.clear(); sSet.sprintf("%02d:%02d", tSet.hour(), tSet.minute()); } tTransit = o->transitTime(T0, geo); // if ( tTransit < tRise ) // tTransit = o->transitTime( JDTomorrow, geo ); sTransit.clear(); sTransit.sprintf("%02d:%02d", tTransit.hour(), tTransit.minute()); WUT->DetailButton->setEnabled(true); } WUT->ObjectRiseLabel->setText(i18n("Rises at: %1", sRise)); WUT->ObjectTransitLabel->setText(i18n("Transits at: %1", sTransit)); WUT->ObjectSetLabel->setText(i18n("Sets at: %1", sSet)); } void WUTDialog::slotCenter() { KStars *kstars = KStars::Instance(); - SkyObject *o = 0; + SkyObject *o = nullptr; // get selected item - if (WUT->ObjectListWidget->currentItem() != 0) + if (WUT->ObjectListWidget->currentItem() != nullptr) { o = kstars->data()->objectNamed(WUT->ObjectListWidget->currentItem()->text()); } - if (o != 0) + if (o != nullptr) { kstars->map()->setFocusPoint(o); kstars->map()->setFocusObject(o); kstars->map()->setDestination(*kstars->map()->focusPoint()); } } void WUTDialog::slotDetails() { KStars *kstars = KStars::Instance(); - SkyObject *o = 0; + SkyObject *o = nullptr; // get selected item - if (WUT->ObjectListWidget->currentItem() != 0) + if (WUT->ObjectListWidget->currentItem() != nullptr) { o = kstars->data()->objectNamed(WUT->ObjectListWidget->currentItem()->text()); } - if (o != 0) + if (o != nullptr) { QPointer detail = new DetailDialog(o, kstars->data()->ut(), geo, kstars); detail->exec(); delete detail; } } void WUTDialog::slotObslist() { - SkyObject *o = 0; + SkyObject *o = nullptr; // get selected item - if (WUT->ObjectListWidget->currentItem() != 0) + if (WUT->ObjectListWidget->currentItem() != nullptr) { o = KStarsData::Instance()->objectNamed(WUT->ObjectListWidget->currentItem()->text()); } - if (o != 0) + if (o != nullptr) { KStarsData::Instance()->observingList()->slotAddObject(o, session); } } void WUTDialog::slotChangeDate() { // Set the time T0 to the evening of today. This will make life easier for the user, who most probably // wants to see what's up on the night of some date, rather than the night of the previous day T0.setTime(QTime(18, 0, 0)); // 6 PM QPointer td = new TimeDialog(T0, KStarsData::Instance()->geo(), this); if (td->exec() == QDialog::Accepted) { T0 = td->selectedDateTime(); // If the time is set to 12 midnight, set it to 00:00:01, so that we don't have date interpretation problems if (T0.time() == QTime(0, 0, 0)) T0.setTime(QTime(0, 0, 1)); //If the Time is earlier than 6:00 am, assume the user wants the night of the previous date if (T0.time().hour() < 6) T0 = T0.addDays(-1); //Now, set time T0 to midnight (of the following day) T0.setTime(QTime(0, 0, 0)); T0 = T0.addDays(1); UT0 = geo->LTtoUT(T0); //Set the Tomorrow date/time to Noon the following day Tomorrow = T0.addSecs(12 * 3600); TomorrowUT = geo->LTtoUT(Tomorrow); //Set the Evening date/time to 6:00 pm Evening = T0.addSecs(-6 * 3600); EveningUT = geo->LTtoUT(Evening); WUT->DateLabel->setText(i18n("The night of %1", QLocale().toString(Evening.date(), QLocale::LongFormat))); init(); slotLoadList(WUT->CategoryListWidget->currentItem()->text()); } delete td; } void WUTDialog::slotChangeLocation() { QPointer ld = new LocationDialog(this); if (ld->exec() == QDialog::Accepted) { GeoLocation *newGeo = ld->selectedCity(); if (newGeo) { geo = newGeo; UT0 = geo->LTtoUT(T0); TomorrowUT = geo->LTtoUT(Tomorrow); EveningUT = geo->LTtoUT(Evening); WUT->LocationLabel->setText(i18n("at %1", geo->fullName())); init(); slotLoadList(WUT->CategoryListWidget->currentItem()->text()); } } delete ld; } void WUTDialog::slotEveningMorning(int index) { if (EveningFlag != index) { EveningFlag = index; init(); slotLoadList(WUT->CategoryListWidget->currentItem()->text()); } } void WUTDialog::updateMag() { m_Mag = WUT->MagnitudeEdit->value(); init(); slotLoadList(WUT->CategoryListWidget->currentItem()->text()); } void WUTDialog::slotChangeMagnitude() { if (timer) { timer->stop(); } else { timer = new QTimer(this); timer->setSingleShot(true); connect(timer, SIGNAL(timeout()), this, SLOT(updateMag())); } timer->start(500); } diff --git a/kstars/widgets/clicklabel.h b/kstars/widgets/clicklabel.h index b7a4b93e2..5ebf4a2d4 100644 --- a/kstars/widgets/clicklabel.h +++ b/kstars/widgets/clicklabel.h @@ -1,46 +1,46 @@ /*************************************************************************** clicklabel.h - description ------------------- begin : Sat 03 Dec 2005 copyright : (C) 2005 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. * * * ***************************************************************************/ #ifndef CLICKLABEL_H #define CLICKLABEL_H #include #include /** @class ClickLabel is a QLabel with a clicked() signal. *@author Jason Harris *@version 1.0 */ class ClickLabel : public QLabel { Q_OBJECT public: - explicit ClickLabel(QWidget *parent = 0, const char *name = 0); + explicit ClickLabel(QWidget *parent = nullptr, const char *name = nullptr); ~ClickLabel() override {} signals: void clicked(); protected: void mousePressEvent(QMouseEvent *e) override { if (e->button() == Qt::LeftButton) emit clicked(); } }; #endif diff --git a/kstars/widgets/dmsbox.h b/kstars/widgets/dmsbox.h index e724b1dcc..2bb8ef6e9 100644 --- a/kstars/widgets/dmsbox.h +++ b/kstars/widgets/dmsbox.h @@ -1,148 +1,148 @@ /*************************************************************************** dmsbox.h - description ------------------- begin : Wed Dec 19 2002 copyright : (C) 2001-2002 by Pablo de Vicente email : vicente@oan.es ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef DMSBOX_H #define DMSBOX_H #include #include //#include #include "dms.h" /** @class dmsBox *A QLineEdit which is capable of displaying and parsing angle values *flexibly and robustly. Angle values can be displayed and parsed as *Degrees or Hours. When displaying a value, it uses a space-delimited *triplet of integers representing the degrees, arcminutes, and arcseconds *of the angle (or hours, minutes, seconds). For example, "-34 45 57". *When parsing a value input by the user, it can also understand *a number of other formats: *@li colon-delimited fields ("-34:45:57") *@li one or two fields ("-35"; "-34 46") *@li fields with unit-labels ("-34d 45m 57s") *@li floating-point numbers ("-34.76583") * *@note Inherits QLineEdit. *@author Pablo de Vicente *@version 1.0 */ class dmsBox : public QLineEdit { Q_OBJECT Q_PROPERTY(bool degType READ degType WRITE setDegType) public: /**Constructor for the dmsBox object. *@param parent pointer to the parent QWidget *@param deg if true use deg/arcmin/arcsec; otherwise * use hours/min/sec. */ explicit dmsBox(QWidget *parent, bool deg = true); /**Destructor (empty)*/ ~dmsBox() override; /**Display an angle using Hours/Min/Sec. *@p t the dms object which is to be displayed */ void showInHours(dms t); /**Display an angle using Hours/Min/Sec. This behaves just *like the above function. It differs only in the data type of *the argument. *@p t pointer to the dms object which is to be displayed */ void showInHours(const dms *t); /**Display an angle using Deg/Arcmin/Arcsec. *@p t the dms object which is to be displayed */ void showInDegrees(dms t); /**Display an angle using Deg/Arcmin/Arcsec. This behaves just *like the above function. It differs only in the data type of *the argument. *@p t pointer to the dms object which is to be displayed */ void showInDegrees(const dms *t); /**Display an angle. Simply calls showInDegrees(t) or *showInHours(t) depending on the value of deg. *@param t the dms object which is to be displayed. *@param deg if true, display Deg/Arcmin/Arcsec; otherwise *display Hours/Min/Sec. */ void show(dms t, bool deg = true); /**Display an angle. Simply calls showInDegrees(t) or *showInHours(t) depending on the value of deg. *This behaves essentially like the above function. It *differs only in the data type of its argument. *@param t the dms object which is to be displayed. *@param deg if true, display Deg/Arcmin/Arcsec; otherwise *display Hours/Min/Sec. */ void show(const dms *t, bool deg = true); /**Simply display a string. *@note JH: Why don't we just use QLineEdit::setText() instead? *@param s the string to display (it need not be a valid angle value). */ void setDMS(const QString &s) { setText(s); } /**Parse the text in the dmsBox as an angle. The text may be an integer *or double value, or it may be a triplet of integer values (separated by spaces *or colons) representing deg/hrs, min, sec. It is also possible to have two *fields. In this case, if the second field is a double, it is converted *to decimal min and double sec. *@param deg if true use deg/arcmin/arcsec; otherwise * use hours/min/sec. *@param ok set to true if a dms object was succedssfully created. *@return a dms object constructed from the fields of the dmsbox */ - dms createDms(bool deg = true, bool *ok = 0); + dms createDms(bool deg = true, bool *ok = nullptr); /** @return a boolean indicating if object contains degrees or hours */ bool degType(void) const { return deg; } /** @short set the dmsBox to Degrees or Hours *@param t if true, the box expects angle values in degrees; otherwise *it expects values in hours */ void setDegType(bool t); /**Clears the QLineEdit */ void clearFields(void) { setDMS(QString()); } inline bool isEmpty() { return EmptyFlag; } protected: void focusInEvent(QFocusEvent *e) override; void focusOutEvent(QFocusEvent *e) override; private slots: void slotTextChanged(const QString &t); private: void setEmptyText(); bool deg, EmptyFlag; dms degValue; }; #endif diff --git a/kstars/widgets/draglistbox.cpp b/kstars/widgets/draglistbox.cpp index 594b9b462..7d8a62aa9 100644 --- a/kstars/widgets/draglistbox.cpp +++ b/kstars/widgets/draglistbox.cpp @@ -1,141 +1,141 @@ /*************************************************************************** draglistbox.cpp - description ------------------- begin : Sun May 29 2005 copyright : (C) 2005 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 "draglistbox.h" #include #include #include #include #include #include DragListBox::DragListBox(QWidget *parent, const char *name) : QListWidget(parent) { if (name) { setObjectName(name); } setAcceptDrops(true); } bool DragListBox::contains(const QString &s) const { QList foundList = findItems(s, Qt::MatchExactly); if (foundList.isEmpty()) return false; else return true; } void DragListBox::dragEnterEvent(QDragEnterEvent *evt) { if (evt->mimeData()->hasText()) { evt->acceptProposedAction(); } else { evt->ignore(); } } void DragListBox::dragMoveEvent(QDragMoveEvent *evt) { if (evt->mimeData()->hasText()) { evt->acceptProposedAction(); } else { evt->ignore(); } } void DragListBox::dropEvent(QDropEvent *evt) { QString text; if (evt->mimeData()->hasText()) { text = evt->mimeData()->text(); //Copy an item dragged from FieldPool to FieldList //If we dragged an "Ignore" item from the FieldPool to the FieldList, then we don't //need to insert the item, because FieldPool already has a persistent Ignore item. if (!(text == i18n("Ignore") && QString(evt->source()->objectName()) == "FieldList" && evt->source() != this)) { QListWidgetItem *lwi = itemAt(evt->pos()); - if (lwi == 0 && evt->pos().y() > visualItemRect(item(count() - 1)).bottom()) + if (lwi == nullptr && evt->pos().y() > visualItemRect(item(count() - 1)).bottom()) { addItem(text); } else { int i = row(itemAt(evt->pos())); insertItem(i, text); } } //Remove an item dragged from FieldList to FieldPool. //If we dragged the "Ignore" item from FieldList to FieldPool, then we don't //want to remove the item from the FieldPool if (!(text == i18n("Ignore") && QString(evt->source()->objectName()) == "FieldPool" && evt->source() != this)) { DragListBox *fp = (DragListBox *)evt->source(); delete fp->takeItem(fp->currentRow()); } } evt->acceptProposedAction(); } void DragListBox::mousePressEvent(QMouseEvent *evt) { QListWidget::mousePressEvent(evt); if (evt->button() == Qt::LeftButton) { leftButtonDown = true; } } void DragListBox::mouseReleaseEvent(QMouseEvent *evt) { QListWidget::mouseReleaseEvent(evt); if (evt->button() == Qt::LeftButton) { leftButtonDown = false; } } void DragListBox::mouseMoveEvent(QMouseEvent *evt) { if (leftButtonDown) { leftButtonDown = false; //Don't create multiple QDrag objects! QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; mimeData->setText(currentItem()->text()); drag->setMimeData(mimeData); drag->exec(); evt->accept(); } } diff --git a/kstars/widgets/fovwidget.cpp b/kstars/widgets/fovwidget.cpp index 02e1b23c2..9bbce407c 100644 --- a/kstars/widgets/fovwidget.cpp +++ b/kstars/widgets/fovwidget.cpp @@ -1,58 +1,58 @@ /*************************************************************************** fovwidget.cpp - description ------------------- begin : Sat 22 Sept 2007 copyright : (C) 2007 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 "fovwidget.h" #include "dialogs/fovdialog.h" #include "fov.h" #include #include -FOVWidget::FOVWidget(QWidget *parent) : QFrame(parent), m_FOV(0) +FOVWidget::FOVWidget(QWidget *parent) : QFrame(parent), m_FOV(nullptr) { } FOVWidget::~FOVWidget() { } void FOVWidget::setFOV(FOV *f) { m_FOV = f; } void FOVWidget::paintEvent(QPaintEvent *) { QPainter p; p.begin(this); p.setRenderHint(QPainter::Antialiasing, true); p.fillRect(contentsRect(), QColor("black")); if (m_FOV && m_FOV->sizeX() > 0 && m_FOV->sizeY() > 0) { m_FOV->draw(p, 0.6 * contentsRect().width(), 0.6 * contentsRect().height()); QFont smallFont = p.font(); smallFont.setPointSize(p.font().pointSize() - 2); p.setFont(smallFont); // TODO: Check if decimal points in this are localized (eg: It should read 1,5 x 1,5 in German rather than 1.5 x 1.5) p.drawText(rect(), Qt::AlignHCenter | Qt::AlignBottom, i18nc("angular size in arcminutes", "%1 x %2 arcmin", QString::number(m_FOV->sizeX(), 'f', 1), QString::number(m_FOV->sizeY(), 'f', 1))); } p.end(); } diff --git a/kstars/widgets/fovwidget.h b/kstars/widgets/fovwidget.h index 8ac65550e..d895e794d 100644 --- a/kstars/widgets/fovwidget.h +++ b/kstars/widgets/fovwidget.h @@ -1,41 +1,41 @@ /*************************************************************************** fovwidget.h - description ------------------- begin : Sat 22 Sept 2007 copyright : (C) 2007 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. * * * ***************************************************************************/ #ifndef FOVWIDGET_H_ #define FOVWIDGET_H_ #include class FOV; class FOVWidget : public QFrame { Q_OBJECT public: - explicit FOVWidget(QWidget *parent = 0); + explicit FOVWidget(QWidget *parent = nullptr); ~FOVWidget() override; void setFOV(FOV *f); protected: void paintEvent(QPaintEvent *e) override; private: FOV *m_FOV; }; #endif diff --git a/kstars/widgets/infoboxwidget.h b/kstars/widgets/infoboxwidget.h index e8efc09ba..3ad8c420c 100644 --- a/kstars/widgets/infoboxwidget.h +++ b/kstars/widgets/infoboxwidget.h @@ -1,118 +1,118 @@ /*************************************************************************** infoboxwidet.h - description ------------------- begin : 20 Aug 2009 copyright : (C) 2009 by Khudyakov Alexey email : alexey.skladnoy@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. * * * ***************************************************************************/ #ifndef INFOBOXWIDGET_H_ #define INFOBOXWIDGET_H_ #include #include #include #include #include class SkyPoint; class SkyObject; class InfoBoxWidget; /** * @brief The InfoBoxes class is a collection of InfoBoxWidget objects that display a transparent box for display of text messages */ class InfoBoxes : public QWidget { Q_OBJECT public: - explicit InfoBoxes(QWidget *parent = 0); + explicit InfoBoxes(QWidget *parent = nullptr); ~InfoBoxes() override; void addInfoBox(InfoBoxWidget *ibox); QList getInfoBoxes() const { return m_boxes; } protected: void resizeEvent(QResizeEvent *event) override; private: QList m_boxes; }; /** * @brief The InfoBoxWidget class is a widget that displays a transparent box for display of text messages. */ class InfoBoxWidget : public QWidget { Q_OBJECT public: /** Alignment of widget. */ enum { NoAnchor = 0, AnchorRight = 1, AnchorBottom = 2, AnchorBoth = 3 }; /** Create one infobox. */ InfoBoxWidget(bool shade, const QPoint &pos, int anchor = 0, const QStringList &str = QStringList(), - QWidget *parent = 0); + QWidget *parent = nullptr); /** Destructor */ ~InfoBoxWidget() override; /** Check whether box is shaded. In this case only one line is shown. */ bool shaded() const { return m_shaded; } /** Get stickyness status of */ int sticky() const { return m_anchor; } /** Adjust widget's postion */ void adjust(); public slots: /** Set information about time. Data is taken from KStarsData. */ void slotTimeChanged(); /** Set information about location. Data is taken from KStarsData. */ void slotGeoChanged(); /** Set information about object. */ void slotObjectChanged(SkyObject *obj); /** Set information about pointing. */ void slotPointChanged(SkyPoint *p); signals: /** Emitted when widget is clicked */ void clicked(); protected: void paintEvent(QPaintEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void showEvent(QShowEvent *event) override; private: /** Uset to set information about object. */ void setPoint(QString name, SkyPoint *p); /** Recalculate size of widet */ void updateSize(); QStringList m_strings; // list of string to show bool m_adjusted; // True if widget coordinates were adjusted bool m_grabbed; // True if widget is dragged around bool m_shaded; // True if widget if shaded int m_anchor; // Vertical alignment of widget static const int padX; static const int padY; }; #endif /* INFOBOXWIDGET_H_ */ diff --git a/kstars/widgets/kshelplabel.h b/kstars/widgets/kshelplabel.h index 43e947df1..94cf3e59a 100644 --- a/kstars/widgets/kshelplabel.h +++ b/kstars/widgets/kshelplabel.h @@ -1,73 +1,73 @@ /*************************************************************************** kshelplabel.h - Help label used to document astronomical terms ------------------- begin : Wed 1 Dec 2010 copyright : (C) 2010 by Valery Kharitonov email : kharvd@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. * * * ***************************************************************************/ #ifndef KSHELPLABEL_H #define KSHELPLABEL_H #include /** Label for displaying links to AstroInfo project * @author Valery Kharitonov */ class KSHelpLabel : public QLabel { Q_OBJECT Q_PROPERTY(QString anchor READ anchor WRITE setAnchor) Q_PROPERTY(QString text READ text WRITE setText) public: /** * Constructor. Creates clear label */ - explicit KSHelpLabel(QWidget *parent = 0); + explicit KSHelpLabel(QWidget *parent = nullptr); /** * Constructor. Creates label with a text and help anchor. * @param text Text of the label * @param anchor Name of the section in the AstroInfo project (without 'ai-') */ - KSHelpLabel(const QString &text, const QString &anchor, QWidget *parent = 0); + KSHelpLabel(const QString &text, const QString &anchor, QWidget *parent = nullptr); QString text() const { return m_cleanText; } void setText(const QString &text); void setAnchor(const QString &anchor); QString anchor() const { return m_anchor; } private slots: /** Open AstroInfo definition of the terms * @param term jargon term */ void slotShowDefinition(const QString &term); private: /** * Updates text with the new anchor */ void updateText(); /** * Anchor in AstroInfo project */ QString m_anchor; /** * String without markup */ QString m_cleanText; }; #endif // KSHELPLABEL_H diff --git a/kstars/widgets/logedit.h b/kstars/widgets/logedit.h index aab73f818..972c610bd 100644 --- a/kstars/widgets/logedit.h +++ b/kstars/widgets/logedit.h @@ -1,43 +1,43 @@ /*************************************************************************** logedit.h - description ------------------- begin : Sat 03 Dec 2005 copyright : (C) 2005 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. * * * ***************************************************************************/ #ifndef LOGEDIT_H #define LOGEDIT_H #include #include /** @class LogEdit is a simple derivative of QTextEdit, that just adds a *focusOut() signal, emitted when the edit loses focus. *@author Jason Harris *@version 1.0 */ class LogEdit : public QTextEdit { Q_OBJECT public: - explicit LogEdit(QWidget *parent = 0); + explicit LogEdit(QWidget *parent = nullptr); ~LogEdit() override {} signals: void focusOut(); protected: void focusOutEvent(QFocusEvent *e) override; }; #endif diff --git a/kstars/widgets/magnitudespinbox.h b/kstars/widgets/magnitudespinbox.h index 469db1323..c7ba6c281 100644 --- a/kstars/widgets/magnitudespinbox.h +++ b/kstars/widgets/magnitudespinbox.h @@ -1,40 +1,40 @@ /*************************************************************************** magnitudespinbox.h - description ------------------- begin : Thu Jul 26 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. * * * ***************************************************************************/ /** @class MagnitudeSpinBox *A special spinbox for entering magnitude values. *This class now inherits QDoubleSpinBox instead of QSpinBox *@short a custom spinbox for magnitude (float) values. *@author Heiko Evermann *@version 1.0 */ #pragma once #include class QWidget; class MagnitudeSpinBox : public QDoubleSpinBox { Q_OBJECT public: /** Default Constructor */ - explicit MagnitudeSpinBox(QWidget *parent = 0); + explicit MagnitudeSpinBox(QWidget *parent = nullptr); /** Constructor. Set minimum and maximum values for the spinbox. */ MagnitudeSpinBox(double minValue, double maxValue, QWidget *parent = nullptr); }; diff --git a/kstars/widgets/mapcanvas.cpp b/kstars/widgets/mapcanvas.cpp index 15759fb5b..8b699b5bd 100644 --- a/kstars/widgets/mapcanvas.cpp +++ b/kstars/widgets/mapcanvas.cpp @@ -1,132 +1,132 @@ /*************************************************************************** mapcanvas.cpp - K Desktop Planetarium ------------------- begin : Tue Apr 10 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 "mapcanvas.h" #include #include #include #include #include #include #include "kspaths.h" #include "dialogs/locationdialog.h" #include "kstars.h" #include "kstarsdata.h" -MapCanvas::MapCanvas(QWidget *parent) : QFrame(parent), ld(0) +MapCanvas::MapCanvas(QWidget *parent) : QFrame(parent), ld(nullptr) { setAutoFillBackground(false); QString bgFile = KSPaths::locate(QStandardPaths::GenericDataLocation, "geomap.png"); bgImage = new QPixmap(bgFile); origin.setX(bgImage->width() / 2); origin.setY(bgImage->height() / 2); } MapCanvas::~MapCanvas() { delete bgImage; } void MapCanvas::setGeometry(int x, int y, int w, int h) { QWidget::setGeometry(x, y, w, h); origin.setX(w / 2); origin.setY(h / 2); } void MapCanvas::setGeometry(const QRect &r) { QWidget::setGeometry(r); origin.setX(r.width() / 2); origin.setY(r.height() / 2); } void MapCanvas::mousePressEvent(QMouseEvent *e) { //Determine Lat/Long corresponding to event press int lng = (e->x() - origin.x()); int lat = (origin.y() - e->y()); if (ld) ld->findCitiesNear(lng, lat); } void MapCanvas::paintEvent(QPaintEvent *) { QPainter p; //prepare the canvas p.begin(this); p.drawPixmap(0, 0, bgImage->scaled(size())); p.setPen(QPen(QColor("SlateGrey"))); //Draw cities QPoint o; foreach (GeoLocation *g, KStarsData::Instance()->getGeoList()) { o.setX(int(g->lng()->Degrees() + origin.x())); o.setY(height() - int(g->lat()->Degrees() + origin.y())); if (o.x() >= 0 && o.x() <= width() && o.y() >= 0 && o.y() <= height()) { p.drawPoint(o.x(), o.y()); } } // FIXME: there must be better way to this. Without bothering LocationDialog if (ld) { //redraw the cities that appear in the filtered list, with a white pen //If the list has not been filtered, skip the redraw. if (ld->filteredList().size()) { p.setPen(Qt::white); foreach (GeoLocation *g, ld->filteredList()) { o.setX(int(g->lng()->Degrees() + origin.x())); o.setY(height() - int(g->lat()->Degrees() + origin.y())); if (o.x() >= 0 && o.x() <= width() && o.y() >= 0 && o.y() <= height()) { p.drawPoint(o.x(), o.y()); } } } GeoLocation *g = ld->selectedCity(); if (g) { o.setX(int(g->lng()->Degrees() + origin.x())); o.setY(height() - int(g->lat()->Degrees() + origin.y())); p.setPen(Qt::red); p.setBrush(Qt::red); p.drawEllipse(o.x() - 3, o.y() - 3, 6, 6); p.drawLine(o.x() - 16, o.y(), o.x() - 8, o.y()); p.drawLine(o.x() + 8, o.y(), o.x() + 16, o.y()); p.drawLine(o.x(), o.y() - 16, o.x(), o.y() - 8); p.drawLine(o.x(), o.y() + 8, o.x(), o.y() + 16); p.setPen(Qt::white); p.setBrush(Qt::white); } } p.end(); } diff --git a/kstars/widgets/thumbimage.h b/kstars/widgets/thumbimage.h index 6982c9513..70c4698c7 100644 --- a/kstars/widgets/thumbimage.h +++ b/kstars/widgets/thumbimage.h @@ -1,62 +1,62 @@ /*************************************************************************** thumbimage.h - description ------------------- begin : Fri 09 Dec 2005 copyright : (C) 2005 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. * * * ***************************************************************************/ #ifndef THUMBIMAGE_H #define THUMBIMAGE_H #include #include class ThumbImage : public QLabel { Q_OBJECT public: - explicit ThumbImage(QWidget *parent, const char *name = 0); + explicit ThumbImage(QWidget *parent, const char *name = nullptr); ~ThumbImage() override; void setImage(QPixmap *pm) { Image = pm; setFixedSize(Image->width(), Image->height()); } QPixmap *image() { return Image; } QPixmap croppedImage(); void setCropRect(int x, int y, int w, int h) { CropRect->setRect(x, y, w, h); } QRect *cropRect() const { return CropRect; } signals: void cropRegionModified(); protected: // void resizeEvent( QResizeEvent *e); void paintEvent(QPaintEvent *) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; private: QRect *CropRect; QPoint *Anchor; QPixmap *Image; bool bMouseButtonDown; bool bTopLeftGrab, bBottomLeftGrab, bTopRightGrab, bBottomRightGrab; int HandleSize; }; #endif diff --git a/kstars/widgets/unitspinboxwidget.h b/kstars/widgets/unitspinboxwidget.h index 956c0db5a..df23137d6 100644 --- a/kstars/widgets/unitspinboxwidget.h +++ b/kstars/widgets/unitspinboxwidget.h @@ -1,56 +1,56 @@ /*************************************************************************** unitspinboxwidget.h - A widget for providing multiple units ------------------- begin : Sun 18th Jan 2015 copyright : (C) 2015 Utkarsh Simha email : utkarshsimha@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. * * * ***************************************************************************/ #ifndef UNITSPINBOXWIDGET_H #define UNITSPINBOXWIDGET_H #include "ui_unitspinboxwidget.h" /** * @brief The UnitSpinBoxWidget class * It is a widget that provides a DoubleSpinBox * and a ComboBox for conversions from different * units. * @author Utkarsh Simha */ class UnitSpinBoxWidget : public QWidget { Q_OBJECT public: - explicit UnitSpinBoxWidget(QWidget *parent = 0); + explicit UnitSpinBoxWidget(QWidget *parent = nullptr); ~UnitSpinBoxWidget() override; /** * @brief addUnit Adds a item to the combo box * @param unitName The name of the unit to be displayed * @param conversionFactor The factor the value of a unit must be multiplied by */ void addUnit(const QString &unitName, double conversionFactor); /** * @brief value Returns value upon conversion */ double value() const; private: Ui::UnitSpinBoxWidget *ui; QComboBox *comboBox; QDoubleSpinBox *doubleSpinBox; }; #endif // UNITSPINBOXWIDGET_H