diff --git a/.gitmodules b/.gitmodules index bffeff0336..a20cf9c446 100644 --- a/.gitmodules +++ b/.gitmodules @@ -116,3 +116,12 @@ [submodule "Modules/jaspPower"] path = Modules/jaspPower url = https://github.com/jasp-stats/jaspPower + branch = master +[submodule "Modules/jaspPredictiveAnalytics"] + path = Modules/jaspPredictiveAnalytics + url = https://github.com/jasp-stats/jaspPredictiveAnalytics + branch = main +[submodule "Modules/jaspLearnStats"] + path = Modules/jaspLearnStats + url = https://github.com/jasp-stats/jaspLearnStats + branch = master diff --git a/Common/utils.cpp b/Common/utils.cpp index 051ffd9f33..9ee7be950a 100644 --- a/Common/utils.cpp +++ b/Common/utils.cpp @@ -36,14 +36,6 @@ using namespace std; -std::string Utils::doubleToString(double dbl, int precision) -{ - std::stringstream conv; //Use this instead of std::to_string to make sure there are no trailing zeroes (and to get full precision) - conv << std::setprecision(precision); - conv << dbl; - return conv.str(); -} - Utils::FileType Utils::getTypeFromFileName(const std::string &path) { @@ -229,7 +221,6 @@ void Utils::sleep(int ms) #endif } - bool Utils::isEqual(const double a, const double b) { if (isnan(a) || isnan(b)) return false; diff --git a/Common/utils.h b/Common/utils.h index 0e83ebb5ce..2852081e58 100644 --- a/Common/utils.h +++ b/Common/utils.h @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include "timers.h" enum class FileTypeBase; @@ -44,15 +46,12 @@ class Utils static bool renameOverwrite( const std::string &oldName, const std::string &newName); static bool removeFile( const std::string &path); - static std::string doubleToString(double dbl, int precision = 10); - static std::filesystem::path osPath(const std::string &path); static std::string osPath(const std::filesystem::path &path); static void remove(std::vector &target, const std::vector &toRemove); static void sleep(int ms); - static bool isEqual(const float a, const float b); static bool isEqual(const double a, const double b); diff --git a/CommonData/column.cpp b/CommonData/column.cpp index cd6f5c3b75..3c698288ba 100644 --- a/CommonData/column.cpp +++ b/CommonData/column.cpp @@ -180,7 +180,7 @@ bool Column::_resetEmptyValuesForScale(std::map &emptyValuesMap) // This value is now considered as empty *doubles = NAN; hasChanged = true; - emptyValuesMap.insert(make_pair(row, Utils::doubleToString(doubleValue))); + emptyValuesMap.insert(make_pair(row, ColumnUtils::doubleToString(doubleValue))); } row++; } @@ -525,7 +525,7 @@ columnTypeChangeResult Column::_changeColumnToNominalOrOrdinal(enum columnType n for (double doubleValue : AsDoubles) if (std::isnan(doubleValue)) values.push_back(""); - else values.push_back(Utils::doubleToString(doubleValue)); + else values.push_back(ColumnUtils::doubleToString(doubleValue)); setColumnAsNominalText(values); return columnTypeChangeResult::changed; @@ -1019,7 +1019,7 @@ string Column::_getScaleValue(int row, bool forDisplay) if (v > std::numeric_limits::max()) return "∞"; else if (v < std::numeric_limits::lowest()) return "-∞"; else if (ColumnUtils::isEmptyValue(v)) return forDisplay ? ColumnUtils::emptyValue : ""; - else return Utils::doubleToString(v); + else return ColumnUtils::doubleToString(v); } string Column::getOriginalValue(int row) @@ -1071,6 +1071,8 @@ void Column::append(int rows) if (rows == 0) return; + size_t originalRowCount = _rowCount; + BlockMap::reverse_iterator itr = _blocks.rbegin(); if (itr == _blocks.rend()) // no blocks @@ -1092,39 +1094,64 @@ void Column::append(int rows) { block->insert(rows); _rowCount += rows; - return; + goto finishingUp; //yeah yeah this is a goto, no problem right? } - block->insert(room); - _rowCount += room; - - int newBlocksRequired = rowsLeft / DataBlock::capacity(); - if (rowsLeft % DataBlock::capacity()) - newBlocksRequired++; - - for (int i = 0; i < newBlocksRequired; i++) + //Extra scope to avoid var init between goto and label { - try { + block->insert(room); + _rowCount += room; - DataBlock *newBlock = _mem->construct(anonymous_instance)(); + int newBlocksRequired = rowsLeft / DataBlock::capacity(); + if (rowsLeft % DataBlock::capacity()) + newBlocksRequired++; - int toInsert = min(rowsLeft, DataBlock::capacity()); - newBlock->insert(toInsert); - rowsLeft -= toInsert; + for (int i = 0; i < newBlocksRequired; i++) + { + try { + + DataBlock *newBlock = _mem->construct(anonymous_instance)(); + + int toInsert = min(rowsLeft, DataBlock::capacity()); + newBlock->insert(toInsert); + rowsLeft -= toInsert; + + id += DataBlock::capacity(); + _blocks.insert(BlockEntry(id, newBlock)); + + _rowCount += toInsert; + } + catch (boost::interprocess::bad_alloc &e) + { + std::cout << e.what() << " "; + std::cout << "append column " << name() << ", append: " << rows << ", rowCount: " << _rowCount << std::endl; + throw e; + } + } + } - id += DataBlock::capacity(); - _blocks.insert(BlockEntry(id, newBlock)); +finishingUp: - _rowCount += toInsert; + //Make sure the new rows are empty looking + switch(_columnType) + { + case columnType::unknown: + case columnType::scale: + for(size_t r=originalRowCount; r<_rowCount; r++) + AsDoubles[r] = NAN; + break; - } - catch (boost::interprocess::bad_alloc &e) - { - std::cout << e.what() << " "; - std::cout << "append column " << name() << ", append: " << rows << ", rowCount: " << _rowCount << std::endl; - throw e; - } + case columnType::ordinal: + case columnType::nominal: + for(size_t r=originalRowCount; r<_rowCount; r++) + AsInts[r] = INT_MIN; + break; + + case columnType::nominalText: + break; } + + } void Column::truncate(int rows) @@ -1138,6 +1165,8 @@ void Column::truncate(int rows) if (rowsToDelete > _rowCount) rowsToDelete = _rowCount; + + std::vector destroyThese; while (rowsToDelete > 0) { @@ -1152,7 +1181,7 @@ void Column::truncate(int rows) rowsToDelete -= block->rowCount(); _rowCount -= block->rowCount(); block->erase(block->rowCount()); - //_mem->destroy_ptr(block); + destroyThese.push_back(block); itr++; if (itr == _blocks.rend()) { @@ -1166,6 +1195,19 @@ void Column::truncate(int rows) } } } + + for(DataBlock * block : destroyThese) + { + _mem->destroy_ptr(block); + + for(const auto & keyval : _blocks) + if(keyval.second == block) + { + _blocks.erase(keyval.first); + break; + } + } + } void Column::setColumnType(enum columnType columnType) diff --git a/CommonData/columns.cpp b/CommonData/columns.cpp index 9788c37354..237a233748 100644 --- a/CommonData/columns.cpp +++ b/CommonData/columns.cpp @@ -63,9 +63,15 @@ void Columns::setRowCount(size_t rowCount) void Columns::setColumnCount(size_t columnCount) { - _columnStore.reserve(columnCount); - for (size_t i = _columnStore.size(); i < columnCount; i++) - _columnStore.push_back(Column(_mem)); + if(columnCount > _columnStore.size()) + { + _columnStore.reserve(columnCount); + for (size_t i = _columnStore.size(); i < columnCount; i++) + _columnStore.push_back(Column(_mem)); + } + else + while(_columnStore.size() > columnCount) + _columnStore.erase(--_columnStore.end()); } @@ -84,6 +90,13 @@ void Columns::removeColumn(std::string name) } } +void Columns::insertColumn(size_t index) +{ + auto c = Column(_mem); + c._setRowCount(maxRowCount()); + _columnStore.insert(_columnStore.begin() + index, c); +} + void Columns::setSharedMemory(boost::interprocess::managed_shared_memory *mem) { diff --git a/CommonData/columns.h b/CommonData/columns.h index 10d34575ba..aad6208bb8 100644 --- a/CommonData/columns.h +++ b/CommonData/columns.h @@ -56,6 +56,7 @@ class Columns void removeColumn(size_t index); void removeColumn(std::string name); + void insertColumn(size_t index); ColumnVector _columnStore; diff --git a/CommonData/columnutils.cpp b/CommonData/columnutils.cpp index 8e5a380752..f4a130e1b3 100644 --- a/CommonData/columnutils.cpp +++ b/CommonData/columnutils.cpp @@ -31,10 +31,10 @@ void ColumnUtils::processEmptyValues() { _currentDoubleEmptyValues.clear(); - for (vector::const_iterator it = _currentEmptyValues.begin(); it != _currentEmptyValues.end(); ++it) + for (const std::string & curEmptyVal : _currentEmptyValues) { double doubleValue; - if (ColumnUtils::getDoubleValue(*it, doubleValue)) + if (ColumnUtils::getDoubleValue(curEmptyVal, doubleValue)) _currentDoubleEmptyValues.push_back(doubleValue); } } @@ -167,6 +167,72 @@ bool ColumnUtils::convertValueToDoubleForImport(const std::string &strValue, dou return true; } +std::string ColumnUtils::doubleToString(double dbl, int precision) +{ + std::stringstream conv; //Use this instead of std::to_string to make sure there are no trailing zeroes (and to get full precision) + conv << std::setprecision(precision); + conv << dbl; + return conv.str(); +} + + +bool ColumnUtils::convertVecToInt(const std::vector &values, std::vector &intValues, std::set &uniqueValues, std::map &emptyValuesMap) +{ + emptyValuesMap.clear(); + uniqueValues.clear(); + intValues.clear(); + intValues.reserve(values.size()); + + int row = 0; + + for (const std::string &value : values) + { + int intValue = std::numeric_limits::lowest(); + + if (ColumnUtils::convertValueToIntForImport(value, intValue)) + { + if (intValue != std::numeric_limits::lowest()) uniqueValues.insert(intValue); + else if (!value.empty()) emptyValuesMap.insert(make_pair(row, value)); + + intValues.push_back(intValue); + } + else + return false; + + row++; + } + + return true; +} + + +bool ColumnUtils::convertVecToDouble(const std::vector &values, std::vector &doubleValues, std::map &emptyValuesMap) +{ + emptyValuesMap.clear(); + doubleValues.clear(); + doubleValues.reserve(values.size()); + + int row = 0; + for (const std::string &value : values) + { + double doubleValue = static_cast(NAN); + + if (ColumnUtils::convertValueToDoubleForImport(value, doubleValue)) + { + doubleValues.push_back(doubleValue); + + if (std::isnan(doubleValue) && value != ColumnUtils::emptyValue) + emptyValuesMap.insert(std::make_pair(row, value)); + } + else + return false; + + row++; + } + + return true; +} + // hex should be 4 hexadecimals characters std::string ColumnUtils::_convertEscapedUnicodeToUTF8(std::string hex) { diff --git a/CommonData/columnutils.h b/CommonData/columnutils.h index c23d20232a..b09e725b91 100644 --- a/CommonData/columnutils.h +++ b/CommonData/columnutils.h @@ -3,7 +3,8 @@ #include #include - +#include +#include class ColumnUtils { @@ -28,6 +29,11 @@ class ColumnUtils static bool convertValueToDoubleForImport( const std::string &strValue, double &doubleValue); static void convertEscapedUnicodeToUTF8( std::string &inputStr); + static bool convertVecToInt(const std::vector &values, std::vector &intValues, std::set &uniqueValues, std::map &emptyValuesMap); + static bool convertVecToDouble(const std::vector &values, std::vector &doubleValues, std::map &emptyValuesMap); + + static std::string doubleToString(double dbl, int precision = 10); + private: static std::string _deEuropeaniseForImport( const std::string &value); static std::string _convertEscapedUnicodeToUTF8( std::string hex); diff --git a/CommonData/labels.cpp b/CommonData/labels.cpp index 5663aae5aa..75eaa01b59 100644 --- a/CommonData/labels.cpp +++ b/CommonData/labels.cpp @@ -271,11 +271,11 @@ const Label &Labels::getLabelObjectFromKey(int index) const return label; } - Log::log() << "Cannot find entry " << index << std::endl; + /*Log::log() << "Cannot find entry " << index << std::endl; for(const Label &label: _labels) { Log::log() << "Label Value: " << label.value() << ", Text: " << label.text() << std::endl; - } + }*/ throw labelNotFound("Cannot find this entry"); } diff --git a/Desktop/analysis/analyses.cpp b/Desktop/analysis/analyses.cpp index 8678906868..58127bde72 100644 --- a/Desktop/analysis/analyses.cpp +++ b/Desktop/analysis/analyses.cpp @@ -22,6 +22,7 @@ #include "processinfo.h" #include "modules/ribbonmodel.h" #include "analysisform.h" +#include "gui/jaspConfiguration/jaspconfiguration.h" #include #include @@ -131,6 +132,11 @@ Analysis* Analyses::create(const Json::Value & analysisData, Modules::AnalysisEn return analysis; } +Analysis *Analyses::create(Modules::AnalysisEntry * analysisEntry, Json::Value* options) +{ + return create(Json::nullValue, analysisEntry, _nextId++, Analysis::Empty, true, "", "", options); +} + void Analyses::storeAnalysis(Analysis* analysis, size_t id, bool notifyAll) { if(_analysisMap.count(id) > 0) @@ -517,8 +523,15 @@ QHash Analyses::roleNames() const Analysis* Analyses::createAnalysis(const QString& module, const QString& analysis) { Modules::DynamicModule * dynamicModule = Modules::DynamicModules::dynMods()->dynamicModule(module.toStdString()); + Json::Value options = JASPConfiguration::getInstance()->getAnalysisOptionValues(module, analysis); - if (dynamicModule) return create(dynamicModule->retrieveCorrespondingAnalysisEntry(fq(analysis))); + if (dynamicModule) + { + if(options != Json::nullValue) + return create(dynamicModule->retrieveCorrespondingAnalysisEntry(fq(analysis)), &options); + else + return create(dynamicModule->retrieveCorrespondingAnalysisEntry(fq(analysis))); + } else return nullptr; } @@ -769,7 +782,9 @@ void Analyses::prepareForLanguageChange() { a->setBeingTranslated(true); a->setRefreshBlocked(true); - a->abort(); + + if(!a->isFinished()) + a->abort(); }); } @@ -785,6 +800,22 @@ void Analyses::languageChangedHandler() emit setResultsMeta(tq(_resultsMeta.toStyledString())); } +void Analyses::dataModeChanged(bool dataMode) +{ + Log::log() << "Data mode turned " << (dataMode ? "on so blocking" : "off so unblocking") << " refresh of analyses." << std::endl; + + applyToAll([&](Analysis * a) + { + a->setRefreshBlocked(dataMode); + + if(dataMode && !a->isFinished()) + a->abort(); + }); + + if(!dataMode) + refreshAllAnalyses(); +} + void Analyses::resultsMetaChanged(QString json) { Json::Reader().parse(fq(json), _resultsMeta); diff --git a/Desktop/analysis/analyses.h b/Desktop/analysis/analyses.h index efb20c31ff..087ac957c0 100644 --- a/Desktop/analysis/analyses.h +++ b/Desktop/analysis/analyses.h @@ -65,6 +65,7 @@ class Analyses : public QAbstractListModel Analysis * createFromJaspFileEntry(Json::Value analysisData, RibbonModel* ribbonModel); Analysis * create(const Json::Value & analysisData, Modules::AnalysisEntry * analysisEntry, size_t id, Analysis::Status status = Analysis::Empty, bool notifyAll = true, std::string title = "", std::string moduleVersion = "", Json::Value *options = nullptr); + Analysis * create(Modules::AnalysisEntry * analysisEntry, Json::Value* options); Analysis * create(Modules::AnalysisEntry * analysisEntry) { return create(Json::nullValue, analysisEntry, _nextId++); } Analysis * operator[](size_t index) { return _analysisMap[_orderedIds[index]]; } @@ -137,6 +138,7 @@ public slots: void allUserDataChanged(QString json); void moveAnalysesResults(Analysis* fromAnalysis, int index); void showRSyntaxInResults(bool show); + void dataModeChanged(bool dataMode); signals: void analysesUnselected(); diff --git a/Desktop/analysis/analysis.cpp b/Desktop/analysis/analysis.cpp index 35867fc1c0..ce4cb4c0bf 100644 --- a/Desktop/analysis/analysis.cpp +++ b/Desktop/analysis/analysis.cpp @@ -29,6 +29,7 @@ #include "gui/preferencesmodel.h" #include "utilities/reporter.h" #include "results/resultsjsinterface.h" +#include "gui/jaspConfiguration/jaspconfiguration.h" Analysis::Analysis(size_t id, Modules::AnalysisEntry * analysisEntry, std::string title, std::string moduleVersion, Json::Value *data) : AnalysisBase(Analyses::analyses(), moduleVersion), @@ -158,6 +159,21 @@ bool Analysis::checkAnalysisEntry() } } +QVariant Analysis::getConstant(const QString& key, const QVariant& defaultValue) const +{ + return JASPConfiguration::getInstance()->getConstant(key, defaultValue, tq(module()), tq(name())); +} + +QVariant Analysis::getConstant(const QString& key, const QVariant& defaultValue, const QString& module, const QString& analysis) const +{ + return JASPConfiguration::getInstance()->getConstant(key, defaultValue, module, analysis); +} + +bool Analysis::optionLocked(const QString& name) const +{ + return JASPConfiguration::getInstance()->optionLocked(tq(module()), tq(this->name()), name); +} + void Analysis::setTitle(const std::string& title) { if (_title != title) diff --git a/Desktop/analysis/analysis.h b/Desktop/analysis/analysis.h index 3e8a7a7ef9..aa2a08f838 100644 --- a/Desktop/analysis/analysis.h +++ b/Desktop/analysis/analysis.h @@ -95,6 +95,12 @@ class Analysis : public AnalysisBase void setEditOptionsOfPlot( const std::string & uniqueName, const Json::Value & editOptions); bool checkAnalysisEntry(); + QVariant getConstant(const QString& key, const QVariant& defaultValue) const override; + QVariant getConstant(const QString& key, const QVariant& defaultValue, const QString& module, const QString& analysis) const override; + bool optionLocked(const QString& name) const override; + + + const Json::Value & results() const { return _results; } const Json::Value & userData() const { return _userData; } const std::string & name() const override { return _name; } diff --git a/Desktop/components/JASP/Widgets/CustomMenu.qml b/Desktop/components/JASP/Widgets/CustomMenu.qml index 478ce976bc..36f6aa504c 100644 --- a/Desktop/components/JASP/Widgets/CustomMenu.qml +++ b/Desktop/components/JASP/Widgets/CustomMenu.qml @@ -95,24 +95,28 @@ FocusScope resultsJsInterface.runJavaScript("window.setSelection(false);") } - function toggle(item, props, x_offset, y_offset) + function toggle(item, props, x_offset = 0, y_offset = 0) { if (item === menu.sourceItem && menu.visible) hide() else - { - menu.sourceItem = item; - menu.menuMaxPos.x = Qt.binding(function() { return mainWindowRoot.width; }); - menu.menuMaxPos.y = Qt.binding(function() { return mainWindowRoot.height; }); - menu.menuMinPos = item.mapToItem(null, 1, 1); - menu.props = props; - menu.menuOffset.x = x_offset; - menu.menuOffset.y = y_offset; - menu.menuScroll = "0,0"; - menu.showMe = true; - - menu.forceActiveFocus(); - } + show(item, props, x_offset, y_offset); + } + + function show(item, props, x_offset = 0, y_offset = 0) + { + menu.sourceItem = item; + menu.menuMaxPos.x = Qt.binding(function() { return mainWindowRoot.width; }); + menu.menuMaxPos.y = Qt.binding(function() { return mainWindowRoot.height; }); + menu.menuMinPos = item.mapToItem(null, 1, 1); + menu.props = props; + menu.menuOffset.x = x_offset; + menu.menuOffset.y = y_offset; + menu.menuScroll = "0,0"; + menu.showMe = true; + + menu.forceActiveFocus(); + } function hide() @@ -242,6 +246,16 @@ FocusScope { sourceComponent : { + if(model.modelData !== undefined) + { + if(model.modelData.startsWith("---")) + { + if(model.modelData == "---") return menuSeparator; + else return menuGroupTitle; + } + return menuDelegate; + } + if (model.isSeparator !== undefined && model.isSeparator) return menuSeparator; else if (model.isGroupTitle !== undefined && model.isGroupTitle) return menuGroupTitle; @@ -257,13 +271,13 @@ FocusScope id: menuItem width: initWidth height: jaspTheme.menuItemHeight - color: !model.isEnabled - ? "transparent" - : mouseArea.pressed || index == currentIndex - ? jaspTheme.buttonColorPressed - : mouseArea.containsMouse - ? jaspTheme.buttonColorHovered - : "transparent" + color: (model.modelData === undefined) && !model.isEnabled + ? "transparent" + : mouseArea.pressed || index == currentIndex + ? jaspTheme.buttonColorPressed + : mouseArea.containsMouse + ? jaspTheme.buttonColorHovered + : "transparent" property double initWidth: (menu.hasIcons ? menuItemImage.width : 0) + menuItemText.implicitWidth + (menu.hasIcons ? menu._iconPad * 5 : menu._iconPad * 4) @@ -273,7 +287,7 @@ FocusScope height : menuItem.height - (2 * menu._iconPad) width : menuItem.height - menu._iconPad - source : menuImageSource + source : model.modelData !== undefined ? "" : menuImageSource smooth : true mipmap : true fillMode : Image.PreserveAspectFit @@ -286,9 +300,9 @@ FocusScope Text { id : menuItemText - text : displayText + text : model.modelData !== undefined ? model.modelData : displayText font : jaspTheme.font - color : isEnabled ? jaspTheme.black : jaspTheme.gray + color : model.modelData !== undefined || isEnabled ? jaspTheme.black : jaspTheme.gray anchors { left : menu.hasIcons ? menuItemImage.right : parent.left @@ -296,7 +310,6 @@ FocusScope rightMargin : menu._iconPad * 2 verticalCenter : parent.verticalCenter } - } MouseArea @@ -305,7 +318,7 @@ FocusScope hoverEnabled : true anchors.fill : parent onClicked : menu.props['functionCall'](index) - enabled : isEnabled + enabled : model.modelData !== undefined || isEnabled } } } @@ -328,7 +341,7 @@ FocusScope height : parent.height - (menu._iconPad * 2) width : height - source : menuImageSource + source : model.modelData !== undefined ? "" : menuImageSource smooth : true mipmap : true fillMode : Image.PreserveAspectFit @@ -346,7 +359,7 @@ FocusScope Text { id : menuItemText - text : displayText + text : model.modelData !== undefined ? model.modelData.substring(3) : displayText font : jaspTheme.fontGroupTitle color : jaspTheme.textEnabled anchors diff --git a/Desktop/components/JASP/Widgets/DataPanel.qml b/Desktop/components/JASP/Widgets/DataPanel.qml index 8726205487..7367274e5c 100644 --- a/Desktop/components/JASP/Widgets/DataPanel.qml +++ b/Desktop/components/JASP/Widgets/DataPanel.qml @@ -1,6 +1,6 @@ -import QtQuick 2.12 import QtQuick.Controls 6.0 import QtQuick.Layouts 1.0 +import QtQuick 2.15 Rectangle @@ -16,7 +16,42 @@ Rectangle anchors.fill: parent anchors.leftMargin: rootDataset.leftHandSpace orientation: Qt.Vertical - handle: Rectangle + + FilterWindow + { + id: filterWindow + objectName: "filterWindow" + SplitView.minimumHeight: desiredMinimumHeight + SplitView.preferredHeight: rootDataset.height * 0.25 + SplitView.maximumHeight: rootDataset.height * 0.8 + + } + + ComputeColumnWindow + { + id: computeColumnWindow + objectName: "computeColumnWindow" + SplitView.minimumHeight: desiredMinimumHeight + SplitView.preferredHeight: rootDataset.height * 0.25 + SplitView.maximumHeight: rootDataset.height * 0.8 + } + + VariablesWindow + { + id: variablesWindow + SplitView.minimumHeight: calculatedMinimumHeight + SplitView.preferredHeight: rootDataset.height * 0.25 + SplitView.maximumHeight: rootDataset.height * 0.8 + } + + DataTableView + { + objectName: "dataSetTableView" + SplitView.fillHeight: true + onDoubleClicked: mainWindow.startDataEditorHandler() + } + + handle: Rectangle { implicitHeight: jaspTheme.splitHandleWidth * 0.8; color: SplitHandle.hovered || SplitHandle.pressed ? jaspTheme.grayLighter : jaspTheme.uiBackground @@ -71,39 +106,5 @@ Rectangle } } } - - FilterWindow - { - id: filterWindow - objectName: "filterWindow" - SplitView.minimumHeight: desiredMinimumHeight - SplitView.preferredHeight: desiredMinimumHeight * 1.5 - SplitView.maximumHeight: rootDataset.height * 0.8 - - } - - ComputeColumnWindow - { - id: computeColumnWindow - objectName: "computeColumnWindow" - SplitView.minimumHeight: desiredMinimumHeight - SplitView.preferredHeight: desiredMinimumHeight * 1.5 - SplitView.maximumHeight: rootDataset.height * 0.8 - } - - VariablesWindow - { - id: variablesWindow - SplitView.minimumHeight: calculatedMinimumHeight - SplitView.preferredHeight: rootDataset.height * 0.25 - SplitView.maximumHeight: rootDataset.height * 0.8 - } - - DataTableView - { - objectName: "dataSetTableView" - SplitView.fillHeight: true - onDoubleClicked: mainWindow.startDataEditorHandler() - } } } diff --git a/Desktop/components/JASP/Widgets/DataTableView.qml b/Desktop/components/JASP/Widgets/DataTableView.qml index 3bae487745..4cf44177ac 100644 --- a/Desktop/components/JASP/Widgets/DataTableView.qml +++ b/Desktop/components/JASP/Widgets/DataTableView.qml @@ -1,7 +1,7 @@ -import QtQuick 2.9 -//import QtQuick.Controls 1.4 as Old -import QtQuick.Controls 2.2 -import JASP.Controls 1.0 as JaspControls +import QtQuick +import QtQuick.Controls +import JASP.Controls as JaspControls +import QtQml.Models FocusScope @@ -20,19 +20,259 @@ FocusScope JASPDataView { - focus: __myRoot.focus + focus: __myRoot.focus - id: dataTableView - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: dataStatusBar.top + id: dataTableView + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: dataStatusBar.top itemHorizontalPadding: 8 * jaspTheme.uiScale itemVerticalPadding: 8 * jaspTheme.uiScale - model: dataSetModel - onDoubleClicked: __myRoot.doubleClicked() + model: dataSetModel + cacheItems: !ribbonModel.dataMode + + doubleClickWorkaround: !ribbonModel.dataMode + //flickableInteractive: !ribbonModel.dataMode + onDoubleClicked: __myRoot.doubleClicked() + + function showCopyPasteMenu(fromItem, globalPos, indexClicked) + { + console.log("showCopyPasteMenu!") + + view.contextMenuClickedAtIndex(indexClicked); + + var ctrlCmd = MACOS ? qsTr("Cmd") : qsTr("Ctrl"); + + var copyPasteMenuModel = + [ + qsTr("Select All"), + + "---", + + qsTr("Cut (%1+X)").arg(ctrlCmd), + qsTr("Copy (%1+C)").arg(ctrlCmd), + qsTr("Paste (%1+V)").arg(ctrlCmd), + + "---", //Works but is ugly: + qsTr("Including header:"), + + qsTr("Header cut (%1+Shift+X)").arg(ctrlCmd), + qsTr("Header copy (%1+Shift+C)").arg(ctrlCmd), + qsTr("Header paste (%1+Shift+V)").arg(ctrlCmd), + + "---", + + qsTr("Select column"), + qsTr("Insert column before"), + qsTr("Insert column after"), + qsTr("Delete column"), + + "---", + + qsTr("Select row"), + qsTr("Insert row before"), + qsTr("Insert row after"), + qsTr("Delete row"), + + ] + + var copyPasteMenuFunctions = + [ + function() { dataTableView.view.selectAll() }, + + function(){}, + + function() { dataTableView.view.cut( false)}, + function() { dataTableView.view.copy( false)}, + function() { dataTableView.view.paste(false)}, + + function (){}, + + function() { dataTableView.view.cut( true )}, + function() { dataTableView.view.copy( true )}, + function() { dataTableView.view.paste(true )}, + + function (){}, + + function() { dataTableView.view.columnSelect( indexClicked.column) }, + function() { dataTableView.view.columnInsertBefore( indexClicked.column) }, + function() { dataTableView.view.columnInsertAfter( indexClicked.column) }, + function() { dataTableView.view.columnDelete( indexClicked.column) }, + + function (){}, + + function() { dataTableView.view.rowSelect( indexClicked.row) }, + function() { dataTableView.view.rowInsertBefore( indexClicked.row) }, + function() { dataTableView.view.rowInsertAfter( indexClicked.row) }, + function() { dataTableView.view.rowDelete( indexClicked.row) } + + ] + + var props = { + "model": copyPasteMenuModel, + "functionCall": function (index) + { + var chosenElement = copyPasteMenuModel[index]; + + // console.log("Option " + chosenElement + " chosen, running function."); + + copyPasteMenuFunctions[index](); + + customMenu.hide() + } + }; + + //customMenu.scrollOri.x = __JASPDataViewRoot.contentX; + //customMenu.scrollOri.y = 0; + + var fromItemPos = fromItem.mapFromGlobal(globalPos.x, globalPos.y) + + customMenu.show(fromItem, props, fromItemPos.x, fromItemPos.y); + + //customMenu.menuScroll.x = Qt.binding(function() { return -1 * (__JASPDataViewRoot.contentX - customMenu.scrollOri.x); }); + //customMenu.menuScroll.y = 0; + //customMenu.menuMinIsMin = true + //customMenu.menuMaxPos.x = __JASPDataViewRoot.width + __JASPDataViewRoot.x + } + + editDelegate: + TextInput + { + id: editItem + text: itemText + color: itemActive ? jaspTheme.textEnabled : jaspTheme.textDisabled + font: jaspTheme.font + verticalAlignment: Text.AlignVCenter + onEditingFinished: finishEdit(); + z: 10 + + + Component.onCompleted: focusTimer.start(); + Timer + { + id: focusTimer + interval: 10 + repeat: false + onTriggered: + { + editItem.forceActiveFocus() + dataTableView.moveItemIntoView(editItem); + } + } + + property bool alreadyFinished: false + + Connections + { + target: ribbonModel + function onFinishCurrentEdit() { finishEdit(); } + } + + function finishEdit() + { + if(!alreadyFinished) + dataTableView.view.editFinished(index, text); + alreadyFinished = true; + } + + Keys.onPressed: (event) => + { + var controlPressed = Boolean(event.modifiers & Qt.ControlModifier); + var shiftPressed = Boolean(event.modifiers & Qt.ShiftModifier ); + var arrowPressed = false; + var arrowIndex; + + switch(event.key) + { + case Qt.Key_C: + if(controlPressed) + { + theView.copy(shiftPressed); + event.accepted = true; + } + break; + + case Qt.Key_X: + if(controlPressed) + { + theView.cut(shiftPressed); + event.accepted = true; + } + break; + + case Qt.Key_V: + if(controlPressed) + { + theView.paste(shiftPressed); + event.accepted = true; + } + break; + + case Qt.Key_A: + if(controlPressed) + { + theView.selectAll(); + event.accepted = true; + } + break; + + case Qt.Key_Home: mainWindowRoot.changeFocusToFileMenu(); break; + + case Qt.Key_Up: if(rowIndex > 0) { arrowPressed = true; arrowIndex = dataSetModel.index(rowIndex - 1, columnIndex); } break; + case Qt.Key_Down: if(rowIndex < dataSetModel.rowCount() - 1) { arrowPressed = true; arrowIndex = dataSetModel.index(rowIndex + 1, columnIndex); } break; + case Qt.Key_Left: if(columnIndex > 0) { arrowPressed = true; arrowIndex = dataSetModel.index(rowIndex, columnIndex - 1); } break; + case Qt.Key_Right: if(columnIndex < dataSetModel.columnCount() - 1) { arrowPressed = true; arrowIndex = dataSetModel.index(rowIndex, columnIndex + 1); } break; + } + + if(arrowPressed) + { + finishEdit(); + + if(!shiftPressed) + dataTableView.view.selectionStart = arrowIndex; + else + { + dataTableView.view.selectionEnd = arrowIndex; + dataTableView.view.edit(arrowIndex); + } + + event.accepted = true; + } + + } + + Rectangle + { + id: highlighter + color: jaspTheme.itemHighlight + z: -1 + visible: ribbonModel.dataMode + anchors + { + fill: parent + topMargin: -dataTableView.itemVerticalPadding + leftMargin: -dataTableView.itemHorizontalPadding + rightMargin: -dataTableView.itemHorizontalPadding + bottomMargin: -dataTableView.itemVerticalPadding + } + + MouseArea + { + z: 1234 + anchors.fill: parent + acceptedButtons: Qt.RightButton + + onPressed: + if(mouse.buttons & Qt.RightButton) + { + finishEdit() + dataTableView.showCopyPasteMenu(editItem, mapToGlobal(mouse.x, mouse.y), dataSetModel.index(rowIndex, columnIndex)); + } + } + } + } itemDelegate: Text @@ -42,6 +282,66 @@ FocusScope color: itemActive ? jaspTheme.textEnabled : jaspTheme.textDisabled font: jaspTheme.font verticalAlignment: Text.AlignVCenter + + MouseArea + { + z: 1234 + hoverEnabled: true + anchors.fill: itemHighlight + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onPressed: (mouse) => + { + if(ribbonModel.dataMode) + { + var shiftPressed = Boolean(mouse.modifiers & Qt.ShiftModifier); + + if(Boolean(mouse.buttons & Qt.RightButton)) + { + forceActiveFocus(); + dataTableView.showCopyPasteMenu(itemHighlight, mapToGlobal(mouse.x, mouse.y), dataSetModel.index(rowIndex, columnIndex)); + } + else + { + if(!shiftPressed) dataTableView.view.selectionStart = dataTableView.view.model.index(rowIndex, columnIndex); + else dataTableView.view.selectionEnd = dataTableView.view.model.index(rowIndex, columnIndex); + + + forceActiveFocus(); + } + } + } + + onPositionChanged: (mouse) => + { + if(ribbonModel.dataMode && Boolean(mouse.modifiers & Qt.ShiftModifier)) + { + var idx = dataTableView.view.model.index(rowIndex, columnIndex) + dataTableView.view.pollSelectScroll(idx) + dataTableView.view.selectionEnd = idx + } + } + + } + + Rectangle + { + id: itemHighlight + visible: ribbonModel.dataMode && (dataTableView.selection.hasSelection, dataTableView.selection.isSelected(dataTableView.view.model.index(rowIndex, columnIndex))) + + color: jaspTheme.itemHighlight + opacity: 1.0 + z: -1 + + anchors + { + fill: parent + topMargin: -dataTableView.itemVerticalPadding + leftMargin: -dataTableView.itemHorizontalPadding + rightMargin: -dataTableView.itemHorizontalPadding + bottomMargin: -dataTableView.itemVerticalPadding + } + } } leftTopCornerItem: @@ -279,6 +579,8 @@ FocusScope labelModel.chosenColumn = columnIndex; labelModel.visible = changedIndex ? true : !labelModel.visible; } + else + dataSetModel.renameColumnDialog(columnIndex); if(dataSetModel.columnUsedInEasyFilter(columnIndex)) { diff --git a/Desktop/components/JASP/Widgets/FileMenu/PrefsAdvanced.qml b/Desktop/components/JASP/Widgets/FileMenu/PrefsAdvanced.qml index eba2bc9156..d0509dd66d 100644 --- a/Desktop/components/JASP/Widgets/FileMenu/PrefsAdvanced.qml +++ b/Desktop/components/JASP/Widgets/FileMenu/PrefsAdvanced.qml @@ -205,9 +205,97 @@ ScrollView toolTip: qsTr("This will erase the 'renv' and 'Modules' folders in the appdata.") onClicked: mainWindow.clearModulesFoldersUser(); - KeyNavigation.tab: logToFile + KeyNavigation.tab: useRemoteConf activeFocusOnTab: true } + + + + CheckBox + { + id: useRemoteConf + label: qsTr("Use remote configuration file.") + checked: preferencesModel.remoteConfiguration + onCheckedChanged: preferencesModel.remoteConfiguration = checked + toolTip: qsTr("Use the remote configuration file pointed to by URL") + + KeyNavigation.tab: remoteConfURL + } + + Item + { + id: remoteConfItem + width: parent.width + height: cranRepoUrl.height + enabled: preferencesModel.remoteConfiguration + + Label + { + id: remoteSettingsLabel + text: qsTr("Configuration URL: ") + + anchors + { + left: parent.left + verticalCenter: parent.verticalCenter + margins: jaspTheme.generalAnchorMargin + } + } + + PrefsTextInput + { + id: remoteConfURL + + text: preferencesModel.remoteConfigurationURL + onEditingFinished: preferencesModel.remoteConfigurationURL = text + + height: browseDeveloperFolderButton.height + anchors + { + left: remoteSettingsLabel.right + right: parent.right + } + + KeyNavigation.tab: localconf + } + } + + Item + { + id: localconf + //enabled: !preferencesModel.remoteConfiguration + width: parent.width + height: browseLocalconfButton.height + + RectangularButton + { + id: browseLocalconfButton + text: qsTr("Select configuration file") + onClicked: preferencesModel.browseConfigurationFile() + anchors.left: parent.left + toolTip: qsTr("Select configuration file.") + + KeyNavigation.tab: browseLocalconfFolderText.textInput + activeFocusOnTab: true + } + + PrefsTextInput + { + id: browseLocalconfFolderText + + text: preferencesModel.localConfigurationPATH + onEditingFinished: preferencesModel.localConfigurationPATH = text + nextEl: logToFile + + height: browseLocalconfButton.height + anchors + { + left: browseLocalconfButton.right + right: parent.right + top: parent.top + } + } + } } PrefsGroupRect diff --git a/Desktop/components/JASP/Widgets/JASPDataView.qml b/Desktop/components/JASP/Widgets/JASPDataView.qml index a987388be9..a4ae700269 100644 --- a/Desktop/components/JASP/Widgets/JASPDataView.qml +++ b/Desktop/components/JASP/Widgets/JASPDataView.qml @@ -8,11 +8,12 @@ import JASP 1.0 FocusScope { id: __JASPDataViewRoot - + property alias view: theView property alias model: theView.model - property alias toolTip: datasetMouseArea.toolTipText - property alias cursorShape: datasetMouseArea.cursorShape - property alias mouseArea: datasetMouseArea + property alias selection: theView.selection + property string toolTip: "" + property alias cursorShape: wheelCatcher.cursorShape + property alias mouseArea: wheelCatcher property bool doubleClickWorkaround: true property alias itemDelegate: theView.itemDelegate @@ -20,37 +21,154 @@ FocusScope property alias columnHeaderDelegate: theView.columnHeaderDelegate property alias leftTopCornerItem: theView.leftTopCornerItem property alias extraColumnItem: theView.extraColumnItem + property alias editDelegate: theView.editDelegate property alias cacheItems: theView.cacheItems property alias itemHorizontalPadding: theView.itemHorizontalPadding property alias itemVerticalPadding: theView.itemVerticalPadding readonly property alias rowNumberWidth: theView.rowNumberWidth + readonly property alias headerHeight: theView.headerHeight readonly property alias contentX: myFlickable.contentX readonly property alias contentY: myFlickable.contentY readonly property alias contentWidth: myFlickable.contentWidth readonly property alias contentHeight: myFlickable.contentHeight + readonly property alias contentX0: myFlickable.contentX + readonly property alias contentY0: myFlickable.contentY + readonly property real contentX1: contentX + myFlickable.width + readonly property real contentY1: contentY + myFlickable.height readonly property alias verticalScrollWidth: vertiScroller.width readonly property alias horizontalScrollHeight: horiScroller.height - ///Aka without a scrollbar + + property alias horiScroller: horiScroller + property alias vertiScroller: vertiScroller + readonly property real flickableWidth: myFlickable.width - ///Aka without a scrollbar readonly property real flickableHeight: myFlickable.height - property alias flickableInteractive: myFlickable.interactive + property real contentFlickSize: 100 + + Keys.onUpPressed: (event) => { budgeUp(); event.accepted = true; } + Keys.onLeftPressed: (event) => { budgeLeft(); event.accepted = true; } + Keys.onDownPressed: (event) => { budgeDown(); event.accepted = true; } + Keys.onRightPressed: (event) => { budgeRight(); event.accepted = true; } + + function budgeUp() { if(myFlickable.contentY0 > 0) myFlickable.contentY = Math.max(0, myFlickable.contentY - contentFlickSize) } + function budgeDown() { if(myFlickable.contentY1 < myFlickable.contentHeight) myFlickable.contentY = Math.min(myFlickable.contentHeight - myFlickable.height, myFlickable.contentY + contentFlickSize) } + function budgeLeft() { if(myFlickable.contentX0 > 0) myFlickable.contentX = Math.max(0, myFlickable.contentX - contentFlickSize) } + function budgeRight() { if(myFlickable.contentX1 < myFlickable.contentWidth) myFlickable.contentX = Math.min(myFlickable.contentWidth - myFlickable.width, myFlickable.contentX + contentFlickSize) } + + + function moveItemIntoView(item) + { + var x0 = item.x - itemHorizontalPadding; + var x1 = item.x + itemHorizontalPadding + item.width; + var y0 = item.y - itemHorizontalPadding; + var y1 = item.y + itemHorizontalPadding + item.height; + + if ( x1 > contentX1 ) myFlickable.contentX = Math.max(rowNumberWidth, x1 - myFlickable.width) ; + else if ( x0 < contentX0 + rowNumberWidth) myFlickable.contentX = x0 - rowNumberWidth ; + if ( y1 > contentY1) myFlickable.contentY = Math.max(headerHeight, y1 - myFlickable.height); + else if ( y0 < contentY0 + headerHeight) myFlickable.contentY = y0 - headerHeight ; + } + + + Keys.onPressed: (event)=> + { + var controlPressed = Boolean(event.modifiers & Qt.ControlModifier); + var shiftPressed = Boolean(event.modifiers & Qt.ShiftModifier ); + + if(controlPressed) + switch(event.key) + { + case Qt.Key_C: + theView.copy(shiftPressed); + event.accepted = true; + break; + + case Qt.Key_X: + theView.cut(shiftPressed); + event.accepted = true; + break; + + case Qt.Key_V: + theView.paste(shiftPressed); + event.accepted = true; + break; + + case Qt.Key_A: + theView.selectAll(); + event.accepted = true; + break; + } + } + + Flickable + { + id: myFlickable + z: -1 + clip: true + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: vertiScroller.left + anchors.bottom: horiScroller.top + + contentHeight: theView.height + contentWidth: theView.width + + DataSetView + { + z: 1 + id: theView + model: null + + /* voor Item + x: -myFlickable.contentX + y: -myFlickable.contentY + viewportX: myFlickable.contentX + viewportY: myFlickable.contentY + viewportW: myFlickable.width //myFlickable.visibleArea.widthRatio * width + viewportH: myFlickable.height //myFlickable.visibleArea.heightRatio * height + */ + + viewportX: myFlickable.contentX + viewportY: myFlickable.contentY + viewportW: myFlickable.visibleArea.widthRatio * width + viewportH: myFlickable.visibleArea.heightRatio * height + + onSelectionBudgesUp: __JASPDataViewRoot.budgeUp() + onSelectionBudgesDown: __JASPDataViewRoot.budgeDown() + onSelectionBudgesLeft: __JASPDataViewRoot.budgeLeft() + onSelectionBudgesRight: __JASPDataViewRoot.budgeRight() + } + } + /* + MouseArea + { + id: wheelCatcher + anchors.fill: myFlickable + acceptedButtons: Qt.NoButton + cursorShape: Qt.PointingHandCursor + z: 1000 + + } + */ + + signal doubleClicked() JASPMouseAreaToolTipped { - id: datasetMouseArea - z: 2 + id: wheelCatcher + z: 1000 anchors.fill: myFlickable anchors.leftMargin: theView.rowNumberWidth anchors.topMargin: theView.headerHeight - toolTipText: doubleClickWorkaround ? qsTr("Double click to edit data") : "" + toolTipText: __JASPDataViewRoot.doubleClickWorkaround ? qsTr("Double click to edit data") : "" - acceptedButtons: doubleClickWorkaround ? Qt.LeftButton : Qt.NoButton + acceptedButtons: __JASPDataViewRoot.doubleClickWorkaround ? Qt.LeftButton : Qt.NoButton dragging: myFlickable.dragging //hoverEnabled: !flickableInteractive @@ -59,7 +177,8 @@ FocusScope onPressed: (mouse)=> { - if(!doubleClickWorkaround) + //console.log("doubleclick workaround pressed") + if(!__JASPDataViewRoot.doubleClickWorkaround) { mouse.accepted = false; return; @@ -69,11 +188,13 @@ FocusScope if(lastTimeClicked === -1 || curTime - lastTimeClicked > doubleClickTime) { + // console.log("doubleclick workaround pressed set time") lastTimeClicked = curTime mouse.accepted = false } else { + // console.log("doubleclick workaround activated") lastTimeClicked = -1 __JASPDataViewRoot.doubleClicked() } @@ -100,40 +221,10 @@ FocusScope else wheel.accepted = false; } - - - } - - signal doubleClicked() - - Flickable - { - id: myFlickable - z: -1 - clip: true - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: vertiScroller.left - anchors.bottom: horiScroller.top - - contentWidth: theView.width - contentHeight: theView.height - - DataSetView - { - z: -1 - id: theView - model: null - - viewportX: myFlickable.visibleArea.xPosition * width - viewportY: myFlickable.visibleArea.yPosition * height - viewportW: myFlickable.visibleArea.widthRatio * width - viewportH: myFlickable.visibleArea.heightRatio * height - } } + JASPScrollBar { id: vertiScroller; diff --git a/Desktop/components/JASP/Widgets/JASPMouseAreaToolTipped.qml b/Desktop/components/JASP/Widgets/JASPMouseAreaToolTipped.qml index 3f53e7049f..19e0a475f9 100644 --- a/Desktop/components/JASP/Widgets/JASPMouseAreaToolTipped.qml +++ b/Desktop/components/JASP/Widgets/JASPMouseAreaToolTipped.qml @@ -3,9 +3,10 @@ import QtQuick.Controls 2.2 MouseArea { - id: theMouseArea - anchors.fill: parent - hoverEnabled: true + id: theMouseArea + //hoverEnabled: true + propagateComposedEvents: true + //preventStealing: true cursorShape: dragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor @@ -25,7 +26,7 @@ MouseArea text: theMouseArea.toolTipText delay: 0 timeout: theMouseArea.toolTipTimeOut - visible: theMouseArea._toolTipVisible + visible: theMouseArea._toolTipVisible && toolTipText != "" x: theMouseArea.mouseX - (width / 2) y: theMouseArea.mouseY + height diff --git a/Desktop/components/JASP/Widgets/MainPage.qml b/Desktop/components/JASP/Widgets/MainPage.qml index 6fc729975d..592173d7c4 100644 --- a/Desktop/components/JASP/Widgets/MainPage.qml +++ b/Desktop/components/JASP/Widgets/MainPage.qml @@ -39,11 +39,12 @@ Item // position of the rest) property bool hasData: mainWindow.dataAvailable - property bool hasAnalysis: mainWindow.analysesAvailable + property bool hasAnalysis: mainWindow.analysesAvailable && !ribbonModel.dataMode function minimizeDataPanel() { handleDataAnalyses.x = 0 + } function maximizeDataPanel() @@ -166,7 +167,7 @@ Item { id: handleAnalysesResults anchors.left: !dragging ? analysesPane.right : undefined - visible: hasAnalysis + visible: hasAnalysis && !ribbonModel.dataMode pointingLeft: analysesModel.visible onArrowClicked: analysesModel.visible = !analysesModel.visible toolTipDrag: hasData ? (handleAnalysesResults.pointingLeft ? qsTr("Resize data/results") : qsTr("Drag to show data")) : "" @@ -225,7 +226,7 @@ Item right: parent.right bottom: parent.bottom } - visible: hasAnalysis + visible: hasAnalysis && !ribbonModel.dataMode color: analysesModel.currentAnalysisIndex !== -1 ? jaspTheme.uiBackground : jaspTheme.white ALTNavigation.enabled: true diff --git a/Desktop/components/JASP/Widgets/MainWindow.qml b/Desktop/components/JASP/Widgets/MainWindow.qml index e81b56f4c7..6754e0195d 100644 --- a/Desktop/components/JASP/Widgets/MainWindow.qml +++ b/Desktop/components/JASP/Widgets/MainWindow.qml @@ -70,7 +70,7 @@ Window function changeFocusToFileMenu() { - ribbon.focus = true; + ribbon.forceActiveFocus(); ribbon.showFileMenuPressed(); } @@ -226,12 +226,9 @@ Window CreateComputeColumnDialog { id: createComputeDialog } ModuleInstaller { id: moduleInstallerDialog } - - PlotEditor - { - id: plotEditingDialog - visible: plotEditorModel.visible - } + ResizeDataDialog { id: resizeDataDialog } + RenameColumnDialog { id: renameColumnDialog } + PlotEditor { id: plotEditingDialog } /*MessageBox { diff --git a/Desktop/components/JASP/Widgets/PlotEditor/PlotEditor.qml b/Desktop/components/JASP/Widgets/PlotEditor/PlotEditor.qml index e70ef608de..2be5de352b 100644 --- a/Desktop/components/JASP/Widgets/PlotEditor/PlotEditor.qml +++ b/Desktop/components/JASP/Widgets/PlotEditor/PlotEditor.qml @@ -21,7 +21,7 @@ Popup onVisibleChanged: plotEditorModel.visible = visible focus: true - Shortcut { onActivated: cancel(); sequence: StandardKey.Cancel; autoRepeat: false; enabled: visible } + Shortcut { onActivated: cancel(); sequences: [StandardKey.Cancel]; autoRepeat: false; enabled: visible } function cancel() { diff --git a/Desktop/components/JASP/Widgets/RenameColumnDialog.qml b/Desktop/components/JASP/Widgets/RenameColumnDialog.qml new file mode 100644 index 0000000000..de2701a598 --- /dev/null +++ b/Desktop/components/JASP/Widgets/RenameColumnDialog.qml @@ -0,0 +1,150 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import JASP.Controls 1.0 + +Popup +{ + id: popupRenameColumnDialog; + modal: true; + + y: (parent.height / 2) - (height / 2) + x: (parent.width / 2) - (width / 2) + width: popupLoader.width + height: popupLoader.height+1 + + property int colIndex; + + background: Rectangle + { + color: jaspTheme.uiBackground + border.color: jaspTheme.uiBorder + border.width: 1 + } + padding: 0 + + Connections + { + target: dataSetModel + function onRenameColumnDialog(columnIndex) + { + console.log("renaming column dialog opened for " + String(columnIndex)) + colIndex = columnIndex; + popupRenameColumnDialog.open() + } + } + + Loader + { + id: popupLoader + sourceComponent: visible ? renameComp : null + visible: popupRenameColumnDialog.opened + } + + Component + { + id: renameComp + + Item + { + height: renameButton.y + renameButton.height + jaspTheme.generalAnchorMargin + width: 200 * jaspTheme.uiScale + + Component.onCompleted: columnName.forceActiveFocus(); + + Text + { + id: title + text: qsTr("Rename column") + font: jaspTheme.fontGroupTitle + color: jaspTheme.textEnabled + verticalAlignment: Text.AlignVCenter + anchors + { + top: parent.top + topMargin: jaspTheme.generalAnchorMargin + horizontalCenter: parent.horizontalCenter + } + } + + + TextInput + { + id: columnName + text: dataSetModel.columnName(popupRenameColumnDialog.colIndex) + color: jaspTheme.textEnabled + font: jaspTheme.fontGroupTitle + selectByMouse: true + anchors + { + top: title.bottom + left: parent.left + right: parent.right + margins: jaspTheme.generalAnchorMargin + } + + onEditingFinished: renameButton.clicked(); + Keys.onEnterPressed: renameButton.clicked(); + + KeyNavigation.tab: renameButton + KeyNavigation.down: renameButton + + Rectangle + { + color: jaspTheme.controlBackgroundColor + border.width: 1 + border.color: jaspTheme.borderColor + radius: jaspTheme.borderRadius + z: -1 + anchors.fill: parent + anchors.margins: -jaspTheme.jaspControlPadding + } + + MouseArea + { + acceptedButtons: Qt.NoButton + anchors.fill: parent + cursorShape: Qt.IBeamCursor + } + } + + RectangularButton + { + id: renameButton + activeFocusOnTab: true + text: qsTr("Rename") + onClicked: { dataSetModel.setColumnName(popupRenameColumnDialog.colIndex, columnName.text); popupRenameColumnDialog.close(); } + toolTip: qsTr("Rename column %1 to %2").arg(popupRenameColumnDialog.colIndex + 1).arg(columnName.text) + KeyNavigation.right: closeButtonCross + KeyNavigation.tab: closeButtonCross + + anchors + { + top: columnName.bottom + margins: jaspTheme.generalAnchorMargin + topMargin: jaspTheme.generalAnchorMargin + jaspTheme.jaspControlPadding + left: parent.left + right: closeButtonCross.left + } + } + + RectangularButton + { + id: closeButtonCross + activeFocusOnTab: true + iconSource: jaspTheme.iconPath + "cross.png" + width: height + height: renameButton.height + onClicked: popupResizeData.close() + toolTip: qsTr("Close without renaming column") + KeyNavigation.up: columnName + + anchors + { + right: parent.right + top: columnName.bottom + margins: jaspTheme.generalAnchorMargin + } + } + } + } +} diff --git a/Desktop/components/JASP/Widgets/ResizeDataDialog.qml b/Desktop/components/JASP/Widgets/ResizeDataDialog.qml new file mode 100644 index 0000000000..c22cbaaec1 --- /dev/null +++ b/Desktop/components/JASP/Widgets/ResizeDataDialog.qml @@ -0,0 +1,185 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import JASP.Controls 1.0 + +Popup +{ + id: popupResizeData; + modal: true; + + y: (parent.height / 2) - (height / 2) + x: (parent.width / 2) - (width / 2) + width: popupLoader.width + height: popupLoader.height+1 + + background: Rectangle + { + color: jaspTheme.uiBackground + border.color: jaspTheme.uiBorder + border.width: 1 + } + padding: 0 + + Connections + { + target: ribbonModel + function onResizeData() { popupResizeData.open() } + } + + Loader + { + id: popupLoader + sourceComponent: visible ? resizeComp : null + visible: popupResizeData.opened + } + + Component + { + id: resizeComp + + Item + { + height: inputs.height + jaspTheme.generalAnchorMargin + (40 * jaspTheme.uiScale) + width: 250 * jaspTheme.uiScale + + Component.onCompleted: cols.forceActiveFocus(); + + Text + { + id: title + text: qsTr("Resize Data") + font: jaspTheme.fontGroupTitle + color: jaspTheme.textEnabled + verticalAlignment: Text.AlignVCenter + anchors + { + top: parent.top + topMargin: jaspTheme.generalAnchorMargin + horizontalCenter: parent.horizontalCenter + } + } + + Item + { + id: inputs + height: 100 * jaspTheme.uiScale + 2 * jaspTheme.generalAnchorMargin + + anchors + { + top: title.bottom + left: parent.left + right: parent.right + margins: jaspTheme.generalAnchorMargin + } + + Label + { + id: colsLabel + text: qsTr("Columns") + anchors + { + top: inputs.top + horizontalCenter: cols.horizontalCenter + } + } + + Label + { + id: rowsLabel + text: qsTr("Rows") + anchors + { + top: inputs.top + horizontalCenter: rows.horizontalCenter + } + } + + Label + { + id: x + text: "X" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors + { + top: colsLabel.bottom + horizontalCenter: parent.horizontalCenter + topMargin: jaspTheme.generalAnchorMargin + } + } + + IntegerField + { + id: cols + value: dataSetModel.columnCount() + fieldWidth: width + anchors + { + top: x.bottom + left: parent.left + right: x.left + margins: jaspTheme.generalAnchorMargin + } + + KeyNavigation.tab: rows + KeyNavigation.right: rows + KeyNavigation.down: resizeButton + } + + IntegerField + { + id: rows + value: dataSetModel.rowCount() + fieldWidth: width + anchors + { + top: x.bottom + left: x.right + right: parent.right + margins: jaspTheme.generalAnchorMargin + } + KeyNavigation.down: closeButtonCross + KeyNavigation.tab: resizeButton + } + } + + RoundedButton + { + id: resizeButton + activeFocusOnTab: true + text: qsTr("Resize") + onClicked: { forceActiveFocus(); dataSetModel.resizeData(rows.value, cols.value); popupResizeData.close(); } + toolTip: qsTr("Resize data to set values") + + KeyNavigation.tab: closeButtonCross + + anchors + { + bottom: inputs.bottom + margins: jaspTheme.generalAnchorMargin + left: parent.left + right: closeButtonCross.left + } + } + + RoundedButton + { + id: closeButtonCross + activeFocusOnTab: true + iconSource: jaspTheme.iconPath + "cross.png" + width: height + height: resizeButton.height + onClicked: popupResizeData.close() + toolTip: qsTr("Close without resizing data") + KeyNavigation.tab: cols + + anchors + { + right: parent.right + bottom: inputs.bottom + margins: jaspTheme.generalAnchorMargin + } + } + } + } +} diff --git a/Desktop/components/JASP/Widgets/Ribbon/RibbonBar.qml b/Desktop/components/JASP/Widgets/Ribbon/RibbonBar.qml index 04cc6616ac..4e5208050c 100644 --- a/Desktop/components/JASP/Widgets/Ribbon/RibbonBar.qml +++ b/Desktop/components/JASP/Widgets/Ribbon/RibbonBar.qml @@ -132,14 +132,14 @@ FocusScope function showModulesMenuPressed() { - modulesPlusButton.focus = true; + modulesPlusButton.forceActiveFocus() modulesPlusButton.showPressed = true; } function showFileMenuPressed() { isFileMenuPressed = true; - fileMenuOpenButton.focus = true; + fileMenuOpenButton.forceActiveFocus(); } Rectangle diff --git a/Desktop/components/JASP/Widgets/Ribbon/RibbonButton.qml b/Desktop/components/JASP/Widgets/Ribbon/RibbonButton.qml index 26a36b10b3..cd3b079d7b 100644 --- a/Desktop/components/JASP/Widgets/Ribbon/RibbonButton.qml +++ b/Desktop/components/JASP/Widgets/Ribbon/RibbonButton.qml @@ -89,6 +89,7 @@ Rectangle { customMenu.hide() ribbonModel.analysisClicked("", "", "", ribbonButton.moduleName) + } else if (ribbonButton.menu.rowCount() === 1) { diff --git a/Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml b/Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml index 776c51a70f..457f6043a0 100644 --- a/Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml +++ b/Desktop/components/JASP/Widgets/Ribbon/Ribbons.qml @@ -29,6 +29,13 @@ Item onActiveFocusChanged: buttonList.focus = true; + Connections + { + target: ribbonModel + + function onDataModeChanged() { focusOut(); } + } + function setCurrentIndex(which, _index=null) { if (which === 'last') @@ -66,7 +73,7 @@ Item while(true) { - if (nextIndex === -1) { + if (nextIndex === -1) { buttonList.currentItem.focus = false; buttonList.currentItem.myMenuOpen = false; showFileMenuPressed(); @@ -172,8 +179,8 @@ Item text: model.moduleTitle listIndex: index moduleName: model.moduleName - source: !model.ribbonButton || model.ribbonButton.iconSource === "" ? "" : (!model.ribbonButton.special ? "file:" : "qrc:/icons/") + model.ribbonButton.iconSource - menu: !model.ribbonButton ? undefined : model.ribbonButton.analysisMenu + source: !model.ribbonButton || model.ribbonButton.iconSource === "" ? "" : (!model.ribbonButton.special ? "file:" : jaspTheme.iconPath ) + model.ribbonButton.iconSource + menu: !model.ribbonButton ? undefined : model.ribbonButton.menu toolTip: !model.ribbonButton ? undefined : model.ribbonButton.toolTip enabled: model.ribbonButton && model.active visible: model.ribbonButton diff --git a/Desktop/components/JASP/Widgets/VariablesWindow.qml b/Desktop/components/JASP/Widgets/VariablesWindow.qml index d08374e690..26b260a012 100644 --- a/Desktop/components/JASP/Widgets/VariablesWindow.qml +++ b/Desktop/components/JASP/Widgets/VariablesWindow.qml @@ -70,18 +70,42 @@ FocusScope anchors.fill: parent anchors.margins: jaspTheme.generalAnchorMargin - Text + TextInput { id: columnNameVariablesWindow text: labelModel.columnName + onTextChanged: if(labelModel.columnName != text) labelModel.columnName = text color: jaspTheme.textEnabled font: jaspTheme.fontGroupTitle + enabled: ribbonModel.dataMode + selectByMouse: true + anchors { horizontalCenter: parent.horizontalCenter top: parent.top topMargin: jaspTheme.generalAnchorMargin } + + Rectangle + { + color: jaspTheme.controlBackgroundColor + border.color: jaspTheme.uiBorder + border.width: 1 + visible: enabled + + anchors.fill: parent + anchors.margins: -1 * jaspTheme.jaspControlPadding + z: -1 + } + + MouseArea + { + acceptedButtons: Qt.NoButton + anchors.fill: parent + cursorShape: Qt.IBeamCursor + } + } Rectangle diff --git a/Desktop/components/JASP/Widgets/qmldir b/Desktop/components/JASP/Widgets/qmldir index fdfddce849..51959f647a 100644 --- a/Desktop/components/JASP/Widgets/qmldir +++ b/Desktop/components/JASP/Widgets/qmldir @@ -34,3 +34,5 @@ ImageInverter 1.0 ImageInverter.qml PlotEditor 1.0 PlotEditor/PlotEditor.qml ErrorMessage 1.0 ErrorMessage.qml EnginesWindow 1.0 EnginesWindow.qml +ResizeDataDialog 1.0 ResizeDataDialog.qml +RenameColumnDialog 1.0 RenameColumnDialog.qml diff --git a/Desktop/data/computedcolumnsmodel.cpp b/Desktop/data/computedcolumnsmodel.cpp index 72b0ee50e5..ae16611e33 100644 --- a/Desktop/data/computedcolumnsmodel.cpp +++ b/Desktop/data/computedcolumnsmodel.cpp @@ -13,6 +13,7 @@ ComputedColumnsModel::ComputedColumnsModel() assert(_singleton == nullptr); _singleton = this; + //Is it really dataSetChanged that needs to connect to datasetLoadedChanged? connect(DataSetPackage::pkg(), &DataSetPackage::dataSetChanged, this, &ComputedColumnsModel::datasetLoadedChanged ); connect(this, &ComputedColumnsModel::datasetLoadedChanged, this, &ComputedColumnsModel::computeColumnJsonChanged ); diff --git a/Desktop/data/datasetpackage.cpp b/Desktop/data/datasetpackage.cpp index 260e907468..06eff8cba3 100644 --- a/Desktop/data/datasetpackage.cpp +++ b/Desktop/data/datasetpackage.cpp @@ -26,9 +26,13 @@ #include "timers.h" #include "utilities/appdirs.h" #include "utils.h" +#include "columnutils.h" #include "utilities/messageforwarder.h" #include "datasetpackagesubnodemodel.h" #include "databaseconnectioninfo.h" +#include "utilities/settings.h" +#include "modules/ribbonmodel.h" + DataSetPackage * DataSetPackage::_singleton = nullptr; @@ -43,6 +47,7 @@ DataSetPackage::DataSetPackage(QObject * parent) : QAbstractItemModel(parent) connect(this, &DataSetPackage::currentFileChanged, this, &DataSetPackage::windowTitleChanged); connect(this, &DataSetPackage::folderChanged, this, &DataSetPackage::windowTitleChanged); connect(this, &DataSetPackage::currentFileChanged, this, &DataSetPackage::nameChanged); + connect(this, &DataSetPackage::dataModeChanged, this, &DataSetPackage::logDataModeChanged); _dataSubModel = new SubNodeModel(parIdxType::dataRoot); _filterSubModel = new SubNodeModel(parIdxType::filterRoot); @@ -116,6 +121,7 @@ void DataSetPackage::reset() endLoadingData(); } + void DataSetPackage::setDataSet(DataSet * dataSet) { if(_dataSet == dataSet) @@ -155,6 +161,31 @@ void DataSetPackage::regenerateInternalPointers() _internalPointers.push_back({parIdxType::label, col}); //these do because they need to know which column is ref'd } +void DataSetPackage::generateEmptyData() +{ + if(_dataSet || isLoaded()) + { + Log::log() << "void DataSetPackage::generateEmptyData() called but dataset already exists, ignoring it." << std::endl; + return; + } + + beginLoadingData(); + createDataSet(); + setDataSetSize(1, 1); + initColumnAsScale(0, freeNewColumnName(0), { NAN }); + endLoadingData(); + emit newDataLoaded(); + resetAllFilters(); +} + +//Some debugprinting +void DataSetPackage::logDataModeChanged(bool dataMode) +{ + Log::log() << "Data Mode " << (dataMode ? "on" : "off") << "!" << std::endl; + beginResetModel(); + endResetModel(); +} + QModelIndex DataSetPackage::index(int row, int column, const QModelIndex &parent) const { const void * pointer = nullptr; @@ -190,9 +221,8 @@ QModelIndex DataSetPackage::index(int row, int column, const QModelIndex &parent Log::log() << "Got a valid parent in DataSetPackage::index but it isn't one of the `*Root`s" << std::endl; break; } - } - + return createIndex(row, column, pointer); } @@ -312,9 +342,10 @@ int DataSetPackage::rowCount(const QModelIndex & parent) const int labelSize = col.labels().size(); return labelSize; } - case parIdxType::filterRoot: [[fallthrough]]; - case parIdxType::dataRoot: return !_dataSet ? 0 : _dataSet->rowCount(); - default: return 0; + + case parIdxType::filter: [[fallthrough]]; + default: [[fallthrough]]; + case parIdxType::data: return !_dataSet ? 0 : _dataSet->rowCount(); } return 0; // <- because gcc is stupid @@ -373,6 +404,8 @@ QVariant DataSetPackage::data(const QModelIndex &index, int role) const if(_dataSet == nullptr || index.column() >= int(_dataSet->columnCount()) || index.row() >= int(_dataSet->rowCount())) return QVariant(); // if there is no data then it doesn't matter what role we play + // Log::log() << "Data requested for col " << index.column() << " and row " << index.row() << std::endl; + switch(role) { case Qt::DisplayRole: @@ -483,8 +516,8 @@ bool DataSetPackage::setData(const QModelIndex &index, const QVariant &value, in return false; case parIdxType::data: - Log::log() << "setData for data is not supported!" << std::endl; - return false; + pasteSpreadsheet(index.row(), index.column(), {{value.toString()}}); + return true; case parIdxType::label: { @@ -915,35 +948,36 @@ int DataSetPackage::setColumnTypeFromQML(int columnIndex, int newColumnType) return int(getColumnType(columnIndex)); } -void DataSetPackage::beginSynchingData() +void DataSetPackage::beginSynchingData(bool informEngines) { - beginLoadingData(); + beginLoadingData(informEngines); _synchingData = true; } -void DataSetPackage::endSynchingDataChangedColumns(std::vector & changedColumns) +void DataSetPackage::endSynchingDataChangedColumns(std::vector & changedColumns, bool hasNewColumns, bool informEngines) { std::vector missingColumns; std::map changeNameColumns; - endSynchingData(changedColumns, missingColumns, changeNameColumns, false, false); + endSynchingData(changedColumns, missingColumns, changeNameColumns, hasNewColumns, informEngines); } void DataSetPackage::endSynchingData(std::vector & changedColumns, std::vector & missingColumns, std::map & changeNameColumns, //origname -> newname bool rowCountChanged, - bool hasNewColumns) + bool hasNewColumns, + bool informEngines) { - endLoadingData(); + endLoadingData(informEngines); _synchingData = false; //We convert all of this stuff to qt containers even though this takes time etc. Because it needs to go through a (queued) connection and it might not work otherwise emit datasetChanged(tq(changedColumns), tq(missingColumns), tq(changeNameColumns), rowCountChanged, hasNewColumns); } -void DataSetPackage::beginLoadingData() +void DataSetPackage::beginLoadingData(bool informEngines) { JASPTIMER_SCOPE(DataSetPackage::beginLoadingData); @@ -951,7 +985,7 @@ void DataSetPackage::beginLoadingData() beginResetModel(); } -void DataSetPackage::endLoadingData() +void DataSetPackage::endLoadingData(bool informEngines) { JASPTIMER_SCOPE(DataSetPackage::endLoadingData); @@ -1082,6 +1116,35 @@ bool DataSetPackage::initColumnAsNominalOrOrdinal( QVariant colID, std::string n return initColumnAsNominalOrOrdinal(colID.toString().toStdString(), newName, values, uniqueValues, is_ordinal); } + +void DataSetPackage::initColumnWithStrings(QVariant colId, std::string newName, const std::vector &values) +{ + // interpret the column as a datatype + std::set uniqueValues; + std::vector intValues; + std::vector doubleValues; + std::map emptyValuesMap; + + //If less unique integers than the thresholdScale then we think it must be ordinal: https://github.com/jasp-stats/INTERNAL-jasp/issues/270 + bool useCustomThreshold = Settings::value(Settings::USE_CUSTOM_THRESHOLD_SCALE).toBool(); + size_t thresholdScale = (useCustomThreshold ? Settings::value(Settings::THRESHOLD_SCALE) : Settings::defaultValue(Settings::THRESHOLD_SCALE)).toUInt(); + + bool valuesAreIntegers = ColumnUtils::convertVecToInt(values, intValues, uniqueValues, emptyValuesMap); + + size_t minIntForThresh = thresholdScale > 2 ? 2 : 0; + + auto isNominalInt = [&](){ return valuesAreIntegers && uniqueValues.size() == minIntForThresh; }; + auto isOrdinal = [&](){ return valuesAreIntegers && uniqueValues.size() > minIntForThresh && uniqueValues.size() <= thresholdScale; }; + auto isScalar = [&](){ return ColumnUtils::convertVecToDouble(values, doubleValues, emptyValuesMap); }; + + if (isOrdinal()) initColumnAsNominalOrOrdinal( colId, newName, intValues, true ); + else if (isNominalInt()) initColumnAsNominalOrOrdinal( colId, newName, intValues, false ); + else if (isScalar()) initColumnAsScale( colId, newName, doubleValues ); + else emptyValuesMap = initColumnAsNominalText( colId, newName, values ); + + storeInEmptyValues(newName, emptyValuesMap); +} + void DataSetPackage::enlargeDataSetIfNecessary(std::function tryThis, const char * callerText) { while(true) @@ -1351,6 +1414,46 @@ std::vector DataSetPackage::getColumnDataDbls(size_t columnIndex) return std::vector(col.AsDoubles.begin(), col.AsDoubles.end()); } +std::vector DataSetPackage::getColumnDataStrs(size_t columnIndex) +{ + if(_dataSet == nullptr) return {}; + + Column & col = _dataSet->column(columnIndex); + + std::vector out; + + for(size_t r=0; rcolumn(columnIndex).setName(newName); + endResetModel(); + + emit datasetChanged({}, {}, QMap({{tq(oldName), tq(newName)}}), false, false); +} + + + void DataSetPackage::setColumnDataInts(size_t columnIndex, std::vector ints) { Column & col = _dataSet->column(columnIndex); @@ -1540,6 +1643,221 @@ void DataSetPackage::columnSetDefaultValues(std::string columnName, columnType c } } +std::string DataSetPackage::freeNewColumnName(size_t startHere) +{ + const QString nameBase = tr("Column %1"); + + while(true) + { + const std::string newColName = fq(nameBase.arg(++startHere)); + if(isColumnNameFree(newColName)) + return newColName; + } +} + +void DataSetPackage::unicifyColumnNames() +{ + for(int c=0; c columnCount(), + newRows = int(rows) > rowCount(); + size_t oriRows = rowCount(), + oriCols = columnCount(); + + beginSynchingData(false); //I assume this is all being called in dataMode, so the engines will be informed once we are done + setDataSetSize(cols, rows); + + for(size_t c=newRows ? 0 : oriCols; c colVals = getColumnDataStrs(c); + std::string colName = getColumnName(c); + + for(size_t r=oriRows; r(), + missing = fq(namesBefore); + + std::map changeNameColumns; + + endSynchingData(changed, missing, changeNameColumns, rowsChanged, newCols, false); + +} + +void DataSetPackage::pasteSpreadsheet(size_t row, size_t col, const std::vector> & cells, QStringList newColNames) +{ + int rowMax = ( cells.size() > 0 ? cells[0].size() : 0), + colMax = cells.size(); + bool rowCountChanged = int(row + rowMax) > rowCount() , + colCountChanged = int(col + colMax) > columnCount() ; + + setSynchingExternally(false); //Don't synch with external file after editing + + //beginResetModel(); + beginSynchingData(false); + + if(colCountChanged || rowCountChanged) + setDataSetSize(std::max(size_t(columnCount()), colMax + col), std::max(size_t(rowCount()), rowMax + row)); + + + + stringvec colNames = getColumnNames(false); + + std::vector changed; + + for(int c=0; c colVals = getColumnDataStrs(dataCol); + + for(int r=0; r c && newColNames[c] != "" ? fq(newColNames[c]) : colName == "" ? freeNewColumnName(dataCol) : colName; + + initColumnWithStrings(dataCol, newName, colVals); + + if(newName != "") + changed.push_back(newName); + + } + + std::map changeNameColumns; + if(newColNames.size() > 0) + { + unicifyColumnNames(); + + stringvec colNamesNew = getColumnNames(false); + + for(size_t i=0; i missingColumns; + + + endSynchingData(changed, missingColumns, changeNameColumns, rowCountChanged, colCountChanged, false); + + if(isLoaded()) setModified(true); +} + +void DataSetPackage::columnInsert(size_t column) +{ + setSynchingExternally(false); //Don't synch with external file after editing + beginSynchingData(false); + + _dataSet->columns().insertColumn(column); + setColumnName(column, freeNewColumnName(column)); + + stringvec changed({getColumnName(column)}); + endSynchingDataChangedColumns(changed, true, false); +} + +void DataSetPackage::columnDelete(size_t column) +{ + setSynchingExternally(false); //Don't synch with external file after editing + beginSynchingData(false); + + std::vector changed; + std::map changeNameColumns; + std::vector missingColumns({getColumnName(column)}); + + _dataSet->columns().removeColumn(column); + + endSynchingData(changed, missingColumns, changeNameColumns, false, true, false); +} + +void DataSetPackage::rowInsert(size_t row) +{ + setSynchingExternally(false); //Don't synch with external file after editing + beginSynchingData(false); + stringvec changed; + + setDataSetSize(columnCount(), rowCount()+1); + + for(int c=0; c colVals = getColumnDataStrs(c); + + colVals.insert(colVals.begin() + row, ""); + + if(int(colVals.size()) > rowCount()) + { + Log::log() << "ASSUMPTION CORRECT! I guess?" << std::endl; + colVals.resize(rowCount()); + } + initColumnWithStrings(c, name, colVals); + } + + std::map changeNameColumns; + std::vector missingColumns; + + endSynchingData(changed, missingColumns, changeNameColumns, true, false, false); +} + + + +void DataSetPackage::rowDelete(size_t row) +{ + setSynchingExternally(false); //Don't synch with external file after editing + beginSynchingData(false); + stringvec changed; + + for(int c=0; c colVals = getColumnDataStrs(c); + + colVals.erase(colVals.begin() + row); + colVals.push_back(""); + + initColumnWithStrings(c, name, colVals); + } + + setDataSetSize(columnCount(), rowCount()-1); + + std::map changeNameColumns; + std::vector missingColumns; + + endSynchingData(changed, missingColumns, changeNameColumns, true, false, false); +} + bool DataSetPackage::createColumn(std::string name, columnType columnType) { if(getColumnIndex(name) >= 0) return false; @@ -1630,9 +1948,35 @@ void DataSetPackage::databaseStartSynching(bool syncImmediately) if(syncImmediately) emit synchingIntervalPassed(); + + emit synchingExternallyChanged(); } } +bool DataSetPackage::synchingExternally() const +{ + return PreferencesModel::prefs()->dataAutoSynchronization() && (_dataFilePath != "" || (_database != Json::nullValue && _databaseIntervalSyncher.isActive())); +} + +void DataSetPackage::setSynchingExternally(bool synchingExternally_) +{ + if(synchingExternally() == synchingExternally_) + return; + + if (!synchingExternally_) + { + PreferencesModel::prefs()->setDataAutoSynchronization(false); + } + else + { + if(_dataFilePath == "") + emit askUserForExternalDataFile(); + else + PreferencesModel::prefs()->setDataAutoSynchronization(true); + } + + emit synchingExternallyChanged(); +} void DataSetPackage::setCurrentFile(QString currentFile) { @@ -1676,7 +2020,6 @@ void DataSetPackage::setFolder(QString folder) emit folderChanged(); } - QString DataSetPackage::name() const { QFileInfo file(_currentFile); @@ -1687,6 +2030,16 @@ QString DataSetPackage::name() const return "JASP"; } +bool DataSetPackage::dataMode() const +{ + return RibbonModel::singleton()->dataMode(); +} + +QModelIndex DataSetPackage::lastCurrentCell() +{ + throw std::runtime_error("Not implemented!"); +} + QString DataSetPackage::windowTitle() const { QString name = DataSetPackage::name(), diff --git a/Desktop/data/datasetpackage.h b/Desktop/data/datasetpackage.h index 4a5577234c..71ab75fc9b 100644 --- a/Desktop/data/datasetpackage.h +++ b/Desktop/data/datasetpackage.h @@ -57,13 +57,15 @@ class DataSetPackageSubNodeModel; class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel because of: https://stackoverflow.com/a/38999940 (And this being a tree model) { Q_OBJECT - Q_PROPERTY(int columnsFilteredCount READ columnsFilteredCount NOTIFY columnsFilteredCountChanged ) - Q_PROPERTY(QString name READ name NOTIFY nameChanged ) - Q_PROPERTY(QString folder READ folder WRITE setFolder NOTIFY folderChanged ) - Q_PROPERTY(QString windowTitle READ windowTitle NOTIFY windowTitleChanged ) - Q_PROPERTY(bool modified READ isModified WRITE setModified NOTIFY isModifiedChanged ) - Q_PROPERTY(bool loaded READ isLoaded WRITE setLoaded NOTIFY loadedChanged ) - Q_PROPERTY(QString currentFile READ currentFile WRITE setCurrentFile NOTIFY currentFileChanged ) + Q_PROPERTY(int columnsFilteredCount READ columnsFilteredCount NOTIFY columnsFilteredCountChanged ) + Q_PROPERTY(QString name READ name NOTIFY nameChanged ) + Q_PROPERTY(QString folder READ folder WRITE setFolder NOTIFY folderChanged ) + Q_PROPERTY(QString windowTitle READ windowTitle NOTIFY windowTitleChanged ) + Q_PROPERTY(bool modified READ isModified WRITE setModified NOTIFY isModifiedChanged ) + Q_PROPERTY(bool loaded READ isLoaded WRITE setLoaded NOTIFY loadedChanged ) + Q_PROPERTY(QString currentFile READ currentFile WRITE setCurrentFile NOTIFY currentFileChanged ) + Q_PROPERTY(bool dataMode READ dataMode NOTIFY dataModeChanged ) + Q_PROPERTY(bool synchingExternally READ synchingExternally WRITE setSynchingExternally NOTIFY synchingExternallyChanged ) typedef std::map> emptyValsType; typedef std::pair intnlPntPair; //first value is what kind of data the index is for and the int is for parIdxType::label only, to know which column is selected. @@ -80,6 +82,7 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau ~DataSetPackage(); void setEngineSync(EngineSync * engineSync); void reset(); + void resizeData(size_t rowCount, size_t columnCount); //Should do reset and such things unlike setDataSetSize void setDataSetSize(size_t columnCount, size_t rowCount); void setDataSetColumnCount(size_t columnCount) { setDataSetSize(columnCount, dataRowCount()); } void setDataSetRowCount(size_t rowCount) { setDataSetSize(dataColumnCount(), rowCount); } @@ -100,17 +103,21 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau SubNodeModel * labelsSubModel () { return _labelsSubModel; } void waitForExportResultsReady(); - - void beginLoadingData(); - void endLoadingData(); - void beginSynchingData(); - void endSynchingDataChangedColumns(std::vector & changedColumns); + + + void beginLoadingData( bool informEngines = true); + void endLoadingData( bool informEngines = true); + void beginSynchingData( bool informEngines = true); + void endSynchingDataChangedColumns(std::vector & changedColumns, bool hasNewColumns = false, bool informEngines = true); void endSynchingData(std::vector & changedColumns, std::vector & missingColumns, std::map & changeNameColumns, //origname -> newname bool rowCountChanged, - bool hasNewColumns); + bool hasNewColumns, bool informEngines = true); + + + void initColumnWithStrings(QVariant colId, std::string newName, const std::vector &values); QHash roleNames() const override; int rowCount( const QModelIndex &parent = QModelIndex()) const override; @@ -140,6 +147,8 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau std::string id() const { return _id; } QString name() const; QString folder() const { return _folder; } + bool dataMode() const; + bool isReady() const { return _analysesHTMLReady; } bool isLoaded() const { return _isLoaded; } bool isArchive() const { return _isArchive; } @@ -174,7 +183,7 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau void setAnalysesData(const Json::Value & analysesData) { _analysesData = analysesData; } void setArchiveVersion(Version archiveVersion) { _archiveVersion = archiveVersion; } void setWarningMessage(std::string message) { _warningMessage = message; } - void setDataFilePath(std::string filePath) { _dataFilePath = filePath; } + void setDataFilePath(std::string filePath) { _dataFilePath = filePath; emit synchingExternallyChanged(); } void setDatabaseJson(const Json::Value & dbInfo); void setInitialMD5(std::string initialMD5) { _initialMD5 = initialMD5; } void setDataFileTimestamp(uint timestamp) { _dataFileTimestamp = timestamp; } @@ -205,6 +214,12 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau std::map initColumnAsNominalText( size_t colNo, std::string newName, const std::vector & values, const std::map & labels = std::map()); std::map initColumnAsNominalText( std::string colName, std::string newName, const std::vector & values, const std::map & labels = std::map()) { return initColumnAsNominalText(_dataSet->getColumnIndex(colName), newName, values, labels); } std::map initColumnAsNominalText( QVariant colID, std::string newName, const std::vector & values, const std::map & labels = std::map()); + + void pasteSpreadsheet(size_t row, size_t column, const std::vector> & cells, QStringList newColNames = QStringList()); + void columnInsert( size_t column ); //Maybe these functions should be made to depend on "columnInsert" etc from AbstractItemModel + void columnDelete( size_t column ); + void rowInsert( size_t row ); + void rowDelete( size_t row ); void columnSetDefaultValues(std::string columnName, columnType colType = columnType::unknown); bool createColumn(std::string name, columnType colType); @@ -239,20 +254,23 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau Json::Value columnToJsonForJASPFile(size_t columnIndex, Json::Value & labelsData, size_t & dataSize); void columnLabelsFromJsonForJASPFile(Json::Value xData, Json::Value columnDesc, size_t columnIndex, std::map > & mapNominalTextValues); - enum columnType getColumnType(std::string columnName) const; - enum columnType getColumnType(size_t columnIndex) const { return _dataSet ? _dataSet->column(columnIndex).getColumnType() : columnType::unknown; } - std::string getColumnName(size_t columnIndex) const { return _dataSet ? _dataSet->column(columnIndex).name() : ""; } - int getColumnIndex(std::string name) const { return !_dataSet ? -1 : _dataSet->getColumnIndex(name); } - int getColumnIndex(QString name) const { return getColumnIndex(name.toStdString()); } - std::vector getColumnDataInts(size_t columnIndex); - std::vector getColumnDataDbls(size_t columnIndex); - void setColumnDataInts(size_t columnIndex, std::vector ints); - void setColumnDataDbls(size_t columnIndex, std::vector dbls); + int getColumnIndex( std::string name) const { return !_dataSet ? -1 : _dataSet->getColumnIndex(name); } + int getColumnIndex( QString name) const { return getColumnIndex(name.toStdString()); } + enum columnType getColumnType( std::string columnName) const; + enum columnType getColumnType( size_t columnIndex) const { return _dataSet ? _dataSet->column(columnIndex).getColumnType() : columnType::unknown; } + std::string getColumnName( size_t columnIndex) const { return _dataSet ? _dataSet->column(columnIndex).name() : ""; } + std::vector getColumnDataInts( size_t columnIndex); + std::vector getColumnDataDbls( size_t columnIndex); + std::vector getColumnDataStrs( size_t columnIndex); + void setColumnName( size_t columnIndex, const std::string & newName); + void setColumnDataInts( size_t columnIndex, std::vector ints); + void setColumnDataDbls( size_t columnIndex, std::vector dbls); size_t getMaximumColumnWidthInCharacters(int columnIndex) const; QStringList getColumnLabelsAsStringList(std::string columnName) const; QStringList getColumnLabelsAsStringList(size_t columnIndex) const; QStringList getColumnValuesAsStringList(size_t columnIndex) const; QList getColumnValuesAsDoubleList(size_t columnIndex) const; + void unicifyColumnNames(); bool setFilterData(std::string filter, std::vector filterResult); void resetFilterAllows(size_t columnIndex); @@ -268,8 +286,7 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau void databaseStartSynching(bool syncImmediately); void databaseStopSynching(); - - + bool synchingExternally() const; signals: void datasetChanged( QStringList changedColumns, @@ -302,6 +319,10 @@ class DataSetPackage : public QAbstractItemModel //Not QAbstractTableModel becau void loadedChanged(); void currentFileChanged(); void synchingIntervalPassed(); + void newDataLoaded(); + void dataModeChanged(bool dataMode); + void synchingExternallyChanged(); + void askUserForExternalDataFile(); public slots: void refresh() { beginResetModel(); endResetModel(); } @@ -312,12 +333,19 @@ public slots: void emptyValuesChangedHandler(); void setCurrentFile(QString currentFile); void setFolder(QString folder); + void generateEmptyData(); + void logDataModeChanged(bool dataMode); + + + void setSynchingExternally(bool synchingExternally); private: ///This function allows you to run some code that changes something in the _dataSet and will try to enlarge it if it fails with an allocation error. Otherwise it might keep going for ever? void enlargeDataSetIfNecessary(std::function tryThis, const char * callerText); bool isThisTheSameThreadAsEngineSync(); bool setAllowFilterOnLabel(const QModelIndex & index, bool newAllowValue); + std::string freeNewColumnName(size_t startHere); + QModelIndex lastCurrentCell(); private: diff --git a/Desktop/data/datasettablemodel.cpp b/Desktop/data/datasettablemodel.cpp index 101bfd7048..3577878048 100644 --- a/Desktop/data/datasettablemodel.cpp +++ b/Desktop/data/datasettablemodel.cpp @@ -17,6 +17,7 @@ // #include "datasettablemodel.h" +#include "utilities/qutils.h" DataSetTableModel* DataSetTableModel::_singleton = nullptr; @@ -71,3 +72,44 @@ QStringList DataSetTableModel::getColumnLabelsAsStringList(int col) const return labels; } + +QString DataSetTableModel::columnName(int column) const +{ + //map to source might be needed here once we start filtering columns + return tq(getColumnName(column)); +} + +void DataSetTableModel::setColumnName(int col, QString name) const +{ + return DataSetPackage::pkg()->setColumnName(col, fq(name)); +} + +void DataSetTableModel::pasteSpreadsheet(size_t row, size_t col, const std::vector > & cells, QStringList newColNames) +{ + QModelIndex idx = mapToSource(index(row, col)); + DataSetPackage::pkg()->pasteSpreadsheet(idx.row(), idx.column(), cells, newColNames); +} + +void DataSetTableModel::columnInsert(size_t column) +{ + QModelIndex idx = mapToSource(index(0, column)); + DataSetPackage::pkg()->columnInsert(idx.column()); +} + +void DataSetTableModel::columnDelete(size_t column) +{ + QModelIndex idx = mapToSource(index(0, column)); + DataSetPackage::pkg()->columnDelete(idx.column()); +} + +void DataSetTableModel::rowInsert(size_t row) +{ + QModelIndex idx = mapToSource(index(row, 0)); + DataSetPackage::pkg()->rowInsert(idx.row()); +} + +void DataSetTableModel::rowDelete(size_t row) +{ + QModelIndex idx = mapToSource(index(row, 0)); + DataSetPackage::pkg()->rowDelete(idx.row()); +} diff --git a/Desktop/data/datasettablemodel.h b/Desktop/data/datasettablemodel.h index f26b4eb6b6..6a7c3cc5da 100644 --- a/Desktop/data/datasettablemodel.h +++ b/Desktop/data/datasettablemodel.h @@ -41,10 +41,14 @@ class DataSetTableModel : public DataSetTableProxy int columnsFilteredCount() const { return DataSetPackage::pkg()->columnsFilteredCount(); } Q_INVOKABLE bool isColumnNameFree(QString name) { return DataSetPackage::pkg()->isColumnNameFree(name); } Q_INVOKABLE QVariant columnTitle(int column) const { return DataSetPackage::pkg()->getColumnTitle(column); } + Q_INVOKABLE QVariant columnIcon(int column) const { return DataSetPackage::pkg()->getColumnIcon(column); } + Q_INVOKABLE QString columnName(int column) const; + Q_INVOKABLE void setColumnName(int col, QString name) const; Q_INVOKABLE QVariant getColumnTypesWithCorrespondingIcon() const { return DataSetPackage::pkg()->getColumnTypesWithCorrespondingIcon(); } Q_INVOKABLE bool columnUsedInEasyFilter(int column) const { return DataSetPackage::pkg()->isColumnUsedInEasyFilter(column); } Q_INVOKABLE void resetAllFilters() { DataSetPackage::pkg()->resetAllFilters(); } Q_INVOKABLE int setColumnTypeFromQML(int columnIndex, int newColumnType) { return DataSetPackage::pkg()->setColumnTypeFromQML(columnIndex, newColumnType); } + Q_INVOKABLE void resizeData(int row, int col) { DataSetPackage::pkg()->resizeData(row, col); } columnType getColumnType(size_t column) const { return DataSetPackage::pkg()->getColumnType(column); } std::string getColumnName(size_t col) const { return DataSetPackage::pkg()->getColumnName(col); } @@ -56,6 +60,12 @@ class DataSetTableModel : public DataSetTableProxy QModelIndex parentModelForType(parIdxType type, int column = 0) const { return DataSetPackage::pkg()->parentModelForType(type, column); } bool synchingData() const { return DataSetPackage::pkg()->synchingData(); } + void pasteSpreadsheet(size_t row, size_t col, const std::vector> & cells, QStringList newColNames = QStringList()); + void columnInsert( size_t column ); + void columnDelete( size_t column ); + void rowInsert( size_t row ); + void rowDelete( size_t row ); + bool showInactive() const { return _showInactive; } signals: @@ -65,6 +75,8 @@ class DataSetTableModel : public DataSetTableProxy void labelChanged(QString columnName, QString originalLabel, QString newLabel); void labelsReordered(QString columnName); + void renameColumnDialog(int columnIndex); + public slots: void setShowInactive(bool showInactive); //void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { if( roles.count(int(DataSetPackage::specialRoles::filter)) > 0) invalidateFilter(); } diff --git a/Desktop/data/importers/importcolumn.cpp b/Desktop/data/importers/importcolumn.cpp index 1b9788a1fc..81835bb633 100644 --- a/Desktop/data/importers/importcolumn.cpp +++ b/Desktop/data/importers/importcolumn.cpp @@ -19,63 +19,6 @@ std::string ImportColumn::name() const return _name; } - -bool ImportColumn::convertVecToInt(const std::vector &values, std::vector &intValues, std::set &uniqueValues, std::map &emptyValuesMap) -{ - emptyValuesMap.clear(); - uniqueValues.clear(); - intValues.clear(); - intValues.reserve(values.size()); - - int row = 0; - - for (const std::string &value : values) - { - int intValue = std::numeric_limits::lowest(); - - if (ColumnUtils::convertValueToIntForImport(value, intValue)) - { - if (intValue != std::numeric_limits::lowest()) uniqueValues.insert(intValue); - else if (!value.empty()) emptyValuesMap.insert(make_pair(row, value)); - - intValues.push_back(intValue); - } - else - return false; - - row++; - } - - return true; -} - -bool ImportColumn::convertVecToDouble(const std::vector &values, std::vector &doubleValues, std::map &emptyValuesMap) -{ - emptyValuesMap.clear(); - doubleValues.clear(); - doubleValues.reserve(values.size()); - - int row = 0; - for (const std::string &value : values) - { - double doubleValue = static_cast(NAN); - - if (ColumnUtils::convertValueToDoubleForImport(value, doubleValue)) - { - doubleValues.push_back(doubleValue); - - if (std::isnan(doubleValue) && value != "") - emptyValuesMap.insert(std::make_pair(row, value)); - } - else - return false; - - row++; - } - - return true; -} - void ImportColumn::changeName(const std::string & name) { Log::log() << "Changing name of column from '" << _name << "' to '" << name << "'\n." << std::endl; diff --git a/Desktop/data/importers/importcolumn.h b/Desktop/data/importers/importcolumn.h index ba0d11e6eb..0981525608 100644 --- a/Desktop/data/importers/importcolumn.h +++ b/Desktop/data/importers/importcolumn.h @@ -23,9 +23,6 @@ class ImportColumn std::string name() const; void changeName(const std::string & name); - static bool convertVecToInt( const std::vector & values, std::vector & intValues, std::set &uniqueValues, std::map &emptyValuesMap); - static bool convertVecToDouble( const std::vector & values, std::vector & doubleValues, std::map &emptyValuesMap); - static bool isStringValueEqual(const std::string &value, Column &col, size_t row); protected: diff --git a/Desktop/data/importers/importer.cpp b/Desktop/data/importers/importer.cpp index 340a123b28..6c5f513962 100644 --- a/Desktop/data/importers/importer.cpp +++ b/Desktop/data/importers/importer.cpp @@ -39,34 +39,6 @@ void Importer::initColumn(QVariant colId, ImportColumn *importColumn) initColumnWithStrings(colId, importColumn->name(), importColumn->allValuesAsStrings()); } -void Importer::initColumnWithStrings(QVariant colId, std::string newName, const std::vector &values) -{ - // interpret the column as a datatype - std::set uniqueValues; - std::vector intValues; - std::vector doubleValues; - std::map emptyValuesMap; - - //If less unique integers than the thresholdScale then we think it must be ordinal: https://github.com/jasp-stats/INTERNAL-jasp/issues/270 - bool useCustomThreshold = Settings::value(Settings::USE_CUSTOM_THRESHOLD_SCALE).toBool(); - size_t thresholdScale = (useCustomThreshold ? Settings::value(Settings::THRESHOLD_SCALE) : Settings::defaultValue(Settings::THRESHOLD_SCALE)).toUInt(); - - bool valuesAreIntegers = ImportColumn::convertVecToInt(values, intValues, uniqueValues, emptyValuesMap); - - size_t minIntForThresh = thresholdScale > 2 ? 2 : 0; - - auto isNominalInt = [&](){ return valuesAreIntegers && uniqueValues.size() == minIntForThresh; }; - auto isOrdinal = [&](){ return valuesAreIntegers && uniqueValues.size() > minIntForThresh && uniqueValues.size() <= thresholdScale; }; - auto isScalar = [&](){ return ImportColumn::convertVecToDouble(values, doubleValues, emptyValuesMap); }; - - if (isOrdinal()) initColumnAsNominalOrOrdinal( colId, newName, intValues, true ); - else if (isNominalInt()) initColumnAsNominalOrOrdinal( colId, newName, intValues, false ); - else if (isScalar()) initColumnAsScale( colId, newName, doubleValues ); - else emptyValuesMap = initColumnAsNominalText( colId, newName, values ); - - storeInEmptyValues(newName, emptyValuesMap); -} - void Importer::syncDataSet(const std::string &locator, boost::function progress) { ImportDataSet *importDataSet = loadFile(locator, progress); diff --git a/Desktop/data/importers/importer.h b/Desktop/data/importers/importer.h index 3c187ee3b7..81a05726db 100644 --- a/Desktop/data/importers/importer.h +++ b/Desktop/data/importers/importer.h @@ -29,7 +29,7 @@ class Importer ///colID can be either an integer (the column index in the data) or a string (the (old) name of the column in the data) virtual void initColumn(QVariant colId, ImportColumn *importColumn); - void initColumnWithStrings(QVariant colId, std::string newName, const std::vector &values); + void initColumnWithStrings(QVariant colId, const std::string & newName, const std::vector & values) { DataSetPackage::pkg()->initColumnWithStrings(colId, newName, values); } ///colID can be either an integer (the column index in the data) or a string (the (old) name of the column in the data) bool initColumnAsNominalOrOrdinal( QVariant colID, std::string newName, const std::vector & values, bool is_ordinal = false) { return DataSetPackage::pkg()->initColumnAsNominalOrOrdinal(colID, newName, values, is_ordinal); } diff --git a/Desktop/data/importers/readstat/readstatimportcolumn.cpp b/Desktop/data/importers/readstat/readstatimportcolumn.cpp index 4152a4a5eb..5f6ce6c42d 100644 --- a/Desktop/data/importers/readstat/readstatimportcolumn.cpp +++ b/Desktop/data/importers/readstat/readstatimportcolumn.cpp @@ -31,7 +31,7 @@ std::string ReadStatImportColumn::valueAsString(size_t row) const switch(_type) { default: return missingValueString(); - case columnType::scale: return Utils::doubleToString(_doubles[row]); + case columnType::scale: return ColumnUtils::doubleToString(_doubles[row]); case columnType::ordinal: [[fallthrough]]; case columnType::nominal: return std::to_string(_ints[row]); case columnType::nominalText: return _strings[row]; @@ -164,14 +164,14 @@ void ReadStatImportColumn::setType(columnType newType) case columnType::nominal: for(double d : _doubles) if(isMissingValue(d)) _ints.push_back(missingValueInt()); - else if(d != double(int(d))) conversionFailed("Double '" + Utils::doubleToString(d) + "' cannot be converted to int."); + else if(d != double(int(d))) conversionFailed("Double '" + ColumnUtils::doubleToString(d) + "' cannot be converted to int."); else _ints.push_back(int(d)); break; case columnType::nominalText: for(double d : _doubles) - _strings.push_back(isMissingValue(d) ? missingValueString() : Utils::doubleToString(d)); + _strings.push_back(isMissingValue(d) ? missingValueString() : ColumnUtils::doubleToString(d)); _doubles.clear(); break; default: break; @@ -345,7 +345,7 @@ void ReadStatImportColumn::addValue(const double & val) [[fallthrough]]; case columnType::nominalText: - addValue(Utils::doubleToString(val)); + addValue(ColumnUtils::doubleToString(val)); break; } } @@ -421,7 +421,7 @@ void ReadStatImportColumn::addLabel(const double & val, const std::string & labe setType(columnType::nominalText); //Because we do not support having doubles as values for labels } - addLabel(Utils::doubleToString(val), label); + addLabel(ColumnUtils::doubleToString(val), label); } void ReadStatImportColumn::addLabel(const std::string & val, const std::string & label) diff --git a/Desktop/data/labelmodel.cpp b/Desktop/data/labelmodel.cpp index a8343f42df..dba20283c9 100644 --- a/Desktop/data/labelmodel.cpp +++ b/Desktop/data/labelmodel.cpp @@ -1,6 +1,7 @@ #include "labelmodel.h" #include "log.h" #include "jasptheme.h" +#include "utilities/qutils.h" LabelModel::LabelModel() : DataSetTableProxy(DataSetPackage::pkg()->labelsSubModel()) { @@ -32,6 +33,20 @@ std::string LabelModel::columnName(size_t col) return DataSetPackage::pkg()->getColumnName(col); } +QString LabelModel::columnNameQ() +{ + return QString::fromStdString(columnName(proxyParentColumn())); +} + + +void LabelModel::setColumnNameQ(QString newColumnName) +{ + if(DataSetPackage::pkg()->columnCount() <= int(proxyParentColumn())) + return; + + return DataSetPackage::pkg()->setColumnName(proxyParentColumn(), fq(newColumnName)); +} + std::vector LabelModel::filterAllows(size_t col) { DataSetPackage * pkg = DataSetPackage::pkg(); @@ -288,7 +303,6 @@ void LabelModel::setSelected(int row, int modifier) } - void LabelModel::unselectAll() { _selected.clear(); diff --git a/Desktop/data/labelmodel.h b/Desktop/data/labelmodel.h index ad618b9afe..bad123895a 100644 --- a/Desktop/data/labelmodel.h +++ b/Desktop/data/labelmodel.h @@ -15,7 +15,7 @@ class LabelModel : public DataSetTableProxy Q_PROPERTY(int filteredOut READ filteredOut NOTIFY filteredOutChanged ) Q_PROPERTY(int chosenColumn READ proxyParentColumn WRITE setProxyParentColumn NOTIFY proxyParentColumnChanged ) Q_PROPERTY(bool visible READ visible WRITE setVisible NOTIFY visibleChanged ) - Q_PROPERTY(QString columnName READ columnNameQ NOTIFY columnNameChanged ) + Q_PROPERTY(QString columnName READ columnNameQ WRITE setColumnNameQ NOTIFY columnNameChanged ) Q_PROPERTY(double rowWidth READ rowWidth WRITE setRowWidth NOTIFY rowWidthChanged ) Q_PROPERTY(double valueMaxWidth READ valueMaxWidth NOTIFY valueMaxWidthChanged ) Q_PROPERTY(double labelMaxWidth READ labelMaxWidth NOTIFY labelMaxWidthChanged ) @@ -25,7 +25,7 @@ class LabelModel : public DataSetTableProxy bool labelNeedsFilter(size_t col); std::string columnName(size_t col); - QString columnNameQ() { return QString::fromStdString(columnName(proxyParentColumn())); } + QString columnNameQ(); bool setData(const QModelIndex & index, const QVariant & value, int role = -1) override; QVariant data( const QModelIndex & index, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; @@ -53,6 +53,7 @@ public slots: void filteredOutChangedHandler(int col); void setVisible(bool visible); void setSelected(int row, int modifier); + void setColumnNameQ(QString newColumnName); void removeAllSelected(); void columnAboutToBeRemoved(int column); void columnDataTypeChanged(const QString & colName); diff --git a/Desktop/engine/enginesync.cpp b/Desktop/engine/enginesync.cpp index 5288ae73b5..0f46f80b7c 100644 --- a/Desktop/engine/enginesync.cpp +++ b/Desktop/engine/enginesync.cpp @@ -501,7 +501,6 @@ void EngineSync::computeColumn(const QString & columnName, const QString & compu void EngineSync::processFilterScript() { - if (!_waitingFilter) return; @@ -964,9 +963,7 @@ void EngineSync::heartbeatTempFiles() } void EngineSync::stopEngines() -{ - _stopProcessing = true; - +{ auto timeout = QDateTime::currentSecsSinceEpoch() + 10; for(EngineRepresentation * e : _engines) @@ -993,6 +990,9 @@ void EngineSync::pauseEngines(bool unloadData) { JASPTIMER_SCOPE(EngineSync::pauseEngines); + + Log::log() << "EngineSync::pauseEngines()" << std::endl; + //make sure we process any received messages first. for(auto * engine : _engines) engine->processReplies(); @@ -1022,12 +1022,12 @@ void EngineSync::startStoppedEngine(EngineRepresentation * engine) void EngineSync::resumeEngines() { JASPTIMER_SCOPE(EngineSync::resumeEngines); - + + Log::log() << "EngineSync::resumeEngines()" << std::endl; + for(EngineRepresentation * engine : _engines) startStoppedEngine(engine); - _stopProcessing = false; - while(!allEnginesResumed()) for (auto * engine : _engines) engine->processReplies(); @@ -1065,6 +1065,23 @@ bool EngineSync::allEnginesInitializing(std::set these) return true; } +void EngineSync::dataModeChanged(bool dataMode) +{ + _stopProcessing = dataMode; + + if(!dataMode) + { + Log::log() << "Data mode turned off, resuming engines." << std::endl; + resumeEngines(); + } + else + { + Log::log() << "Data mode turned on, pausing engines." << std::endl; + pauseEngines(); + } + +} + void EngineSync::enginesPrepareForData() { JASPTIMER_SCOPE(EngineSync::enginesPrepareForData); diff --git a/Desktop/engine/enginesync.h b/Desktop/engine/enginesync.h index 58caf45d67..97344b42d0 100644 --- a/Desktop/engine/enginesync.h +++ b/Desktop/engine/enginesync.h @@ -54,8 +54,6 @@ class EngineSync : public QAbstractListModel EngineRepresentation * createNewEngine(bool addToEngines = true, int overrideChannel = -1); EngineRepresentation * createRCmdEngine(); - std::string currentStateForDebug() const; - int rowCount(const QModelIndex & = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; @@ -85,6 +83,9 @@ public slots: void enginesPrepareForData(); void enginesReceiveNewData(); bool isModuleInstallRequestActive(const QString & moduleName); + std::string currentStateForDebug() const; + void dataModeChanged(bool dataMode); + signals: void processNewFilterResult(const std::vector & filterResult, int requestID); diff --git a/Desktop/gui/jaspConfiguration/jaspconfiguration.cpp b/Desktop/gui/jaspConfiguration/jaspconfiguration.cpp new file mode 100644 index 0000000000..6f5eca66e3 --- /dev/null +++ b/Desktop/gui/jaspConfiguration/jaspconfiguration.cpp @@ -0,0 +1,212 @@ +#include "jaspconfiguration.h" +#include "jaspconfigurationparser.h" +#include "utilities/settings.h" +#include "gui/preferencesmodel.h" +#include "log.h" +#include +#include +#include +#include +#include +#include +#include +#include + +JASPConfiguration* JASPConfiguration::_instance = nullptr; + +JASPConfiguration* JASPConfiguration::getInstance(QObject *parent) +{ + if(!_instance) + _instance = new JASPConfiguration(parent); + return _instance; +} + + +JASPConfiguration::JASPConfiguration(QObject *parent) + : QObject{parent} +{ +} + +bool JASPConfiguration::constantExists(const QString &constant, const QString &module, const QString &analysis) +{ + return _definedConstants.contains(module) && _definedConstants[module].contains(analysis) && _definedConstants[module][analysis].contains(constant); +} + +QVariant JASPConfiguration::getConstant(const QString &constant, const QVariant& defaultValue, const QString &module, const QString &analysis) +{ + if(constantExists(constant, module, analysis)) + return _definedConstants[module][analysis][constant]; + else if(constantExists(constant, module)) + return _definedConstants[module][""][constant]; + else if(constantExists(constant)) + return _definedConstants[""][""][constant]; + return defaultValue; +} + +bool JASPConfiguration::optionSet(const QString &module, const QString &analysis, const QString &optionName) +{ + return _analysisOptions.contains(module) && _analysisOptions[module].contains(analysis) && _analysisOptions[module][analysis].isMember(optionName.toStdString()); +} + +bool JASPConfiguration::optionsSet(const QString &module, const QString &analysis) +{ + return _analysisOptions.contains(module) && _analysisOptions[module].contains(analysis); +} + +bool JASPConfiguration::optionLocked(const QString &module, const QString &analysis, const QString &optionName) +{ + return _analysisOptionsLocked[module][analysis][optionName]; +} + +Json::Value JASPConfiguration::getAnalysisOptionValues(const QString &module, const QString &analysis) +{ + if(optionsSet(module, analysis)) + return _analysisOptions[module][analysis]; + return Json::nullValue; +} + +void JASPConfiguration::processConfiguration() +{ + clear(); + bool localOK = processLocal(); + + //read, parse & save remote settings + if(Settings::value(Settings::REMOTE_CONFIGURATION).toBool()) + { + auto conn = std::make_shared(); + *conn = connect(&_networkManager, &QNetworkAccessManager::finished, this, [=, this](QNetworkReply* reply) { + QObject::disconnect(*conn); + reply->deleteLater(); + + try + { + if(reply->error()) + throw std::runtime_error("Error fetching remote configuration file " + reply->request().url().toString().toStdString() + " : " + reply->errorString().toStdString()); + QByteArray payload = reply->readAll(); + clear(); + if(!JASPConfigurationParser::getParser(JASPConfigurationParser::Format::TOML)->parse(this, payload)) + throw std::runtime_error("Parsing failed"); + + auto conf = getDefaultConfFile(true); + conf->write(payload); + conf->close(); + Log::log() << "Stored local copy of remote configuration" << std::endl; + emit this->configurationProcessed("REMOTE"); + } + catch (std::runtime_error& e) + { + Log::log() << "Failed to process remote configuration: " << e.what() << std::endl; + if(!localOK) + emit this->configurationProcessed("FAIL"); + else + emit this->configurationProcessed("LOCAL"); + return; + } + }); + + //make the request + QNetworkRequest request(Settings::value(Settings::REMOTE_CONFIGURATION_URL).toString()); + QNetworkReply* reply = _networkManager.get(request); + connect(reply, &QNetworkReply::sslErrors, this, &JASPConfiguration::sslErrors); + } + else + emit configurationProcessed("LOCAL"); +} + +bool JASPConfiguration::processLocal() +{ + try + { + auto localConfFile = getLocalConfFile(); + QTextStream in(localConfFile.get()); + if(!JASPConfigurationParser::getParser(JASPConfigurationParser::Format::TOML)->parse(this, in.readAll())) + throw std::runtime_error("Parsing failed"); + } + catch(std::runtime_error& e) + { + Log::log() << "Could not parse local configuration: " << e.what() << std::endl; + return false; + } + return true; +} + +void JASPConfiguration::clear() +{ + _definedConstants.clear(); + _modulesToLoad.clear(); +} + +void JASPConfiguration::remoteChanged() +{ + processConfiguration(); +} + +std::shared_ptr JASPConfiguration::getLocalConfFile(bool truncate) { + + //the right default path can only be determined here, so after jasp init + if(PreferencesModel::prefs()->localConfigurationPATH() == "") + { + defaultConfigurationFolder = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); + defaultConfigurationPath = defaultConfigurationFolder + "/" + defaultConfigurationFilename; + PreferencesModel::prefs()->setLocalConfigurationPATH(defaultConfigurationPath); + } + + if(Settings::value(Settings::REMOTE_CONFIGURATION).toBool()) //if we use a remote but we are offline + return getDefaultConfFile(truncate); + else //user specified local file + return getConfFile(Settings::value(Settings::LOCAL_CONFIGURATION_PATH).toString(), truncate); +} + +std::shared_ptr JASPConfiguration::getDefaultConfFile(bool truncate) +{ + QDir confDir; + defaultConfigurationFolder = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); + defaultConfigurationPath = defaultConfigurationFolder + "/" + defaultConfigurationFilename; + if(!confDir.mkpath(defaultConfigurationFolder)) + throw std::runtime_error("Could not access app configuration path"); + + return getConfFile(defaultConfigurationPath, truncate); +} + +std::shared_ptr JASPConfiguration::getConfFile(QString path, bool truncate) +{ + std::shared_ptr localConfFile = std::make_shared(path); + QIODeviceBase::OpenMode flags = QIODeviceBase::ReadWrite | QIODeviceBase::Text | (truncate ? QIODeviceBase::Truncate : QIODeviceBase::NotOpen); + if (!localConfFile->open(flags)) + throw std::runtime_error("Could not open local configuration file " + path.toStdString() + ": " + localConfFile->errorString().toStdString()); + return localConfFile; +} + +bool JASPConfiguration::addConstant(QString key, QVariant value, QString moduleName, QString analysisName) +{ + _definedConstants[moduleName][analysisName][key] = value; + return true; +} + +bool JASPConfiguration::addOption(QString key, QVariant value, bool locked, QString moduleName, QString analysisName) +{ + _analysisOptionsLocked[moduleName][analysisName][key] = locked; + if(!(_analysisOptions.contains(moduleName) && _analysisOptions[moduleName].contains(analysisName))) + _analysisOptions[moduleName][analysisName] = Json::Value(Json::objectValue); + + //TODO fix very suboptimal way of doing things + if(value.userType() == QMetaType::Double) + _analysisOptions[moduleName][analysisName][key.toStdString()] = value.value(); + else if(value.userType() == QMetaType::LongLong) + _analysisOptions[moduleName][analysisName][key.toStdString()] = static_cast(value.value()); + else if(value.userType() == QMetaType::Bool) + _analysisOptions[moduleName][analysisName][key.toStdString()] = value.value(); + else if(value.canConvert()) + _analysisOptions[moduleName][analysisName][key.toStdString()] = value.toString().toStdString(); + + return true; +} + +void JASPConfiguration::sslErrors(const QList &errors) +{ + for (const QSslError &error : errors) + Log::log() << "Error fetching remote settings:" << error.errorString().toStdString() << std::endl; +} + + + diff --git a/Desktop/gui/jaspConfiguration/jaspconfiguration.h b/Desktop/gui/jaspConfiguration/jaspconfiguration.h new file mode 100644 index 0000000000..69631cb700 --- /dev/null +++ b/Desktop/gui/jaspConfiguration/jaspconfiguration.h @@ -0,0 +1,103 @@ +/*! @file altnavigation.h + * @brief Defines a singleton that can be commanded to gather and parse local and remote configuration files. + * It provides an interface that allows querying of the configuration data from both qml and cpp environments. + * Configuration includes: Modules to enable, analysis options, qml runtime constants, startup commands, etc. + * + * Defines a singleton that can be commanded to gather and parse local and remote configuration files. + * It provides an interface that allows querying of the configuration data from both qml and cpp environments. + * Configuration includes: Modules to enable, analysis options, qml runtime constants, startup commands,etc. + * + * This subsystem will parse the local configuration file. + * Additionally when a remote is provided in settings, the remote configuration and override + * the local configuration file with the remote as a local cache (in case of offline JASP use) + * + * This subsystem is made of this frontend class along with a Parser base class. + * This class contains a factory function and associated enum so that additional formats can be added easily. + * The parser classes make use of the interface of the JASPConfiguration class to fill in the internal data structures. + * + * + * @author Rens Dofferhoff + */ + +#ifndef JASPCONFIGURATION_H +#define JASPCONFIGURATION_H + +#include +#include +#include +#include +#include "version.h" +#include "json/json.h" + +class JASPConfiguration : public QObject +{ + Q_OBJECT +public: + //read and parse local and remote configuration + void processConfiguration(); + + //QML programming constants interface + Q_INVOKABLE bool constantExists(const QString& constant, const QString& module = "", const QString& analysis = ""); + Q_INVOKABLE QVariant getConstant(const QString& constant, const QVariant& defaultValue = QVariant(), const QString& module = "", const QString& analysis = ""); + + //C++ configuration access functions + bool optionSet(const QString& module, const QString& analysis, const QString& optionName); + bool optionsSet(const QString& module, const QString& analysis); + bool optionLocked(const QString& module, const QString& analysis, const QString& optionName); + Json::Value getAnalysisOptionValues(const QString& module, const QString& analysis); + const QStringList* getAdditionalModules() { return &_modulesToLoad; } + QString getStartupCommands() { return _startupCommands; } + + + //Parser set functions + bool addConstant(QString key, QVariant value, QString moduleName = "", QString analysisName = ""); + bool addOption(QString key, QVariant value, bool locked, QString moduleName = "", QString analysisName = ""); + void setAdditionalModule(const QString& module) { _modulesToLoad.push_back(module); }; + void setAdditionalModules(const QStringList& modules) { _modulesToLoad += modules; }; + void setStartupCommands(const QString& commands) { _startupCommands += commands; }; + void setJASPVersion(const Version& v) { _jaspVersion = v; }; + + + //singleton stuff + static JASPConfiguration* getInstance(QObject *parent = nullptr); + JASPConfiguration(JASPConfiguration& other) = delete; + void operator=(const JASPConfiguration&) = delete; + + QString getDefaultConfigurationPath() { return defaultConfigurationPath; }; + +public slots: + void remoteChanged(); + + +signals: + void configurationProcessed(QString result); + +private slots: + void sslErrors(const QList &errors); + +private: + bool processLocal(); + void clear(); + + std::shared_ptr getLocalConfFile(bool truncate = false); + std::shared_ptr getDefaultConfFile(bool truncate = false); + std::shared_ptr getConfFile(QString path, bool truncate = false); + + QNetworkAccessManager _networkManager; + + Version _jaspVersion; + QMap>> _definedConstants; + QMap> _analysisOptions; + QMap>> _analysisOptionsLocked; + QString _startupCommands; + QStringList _modulesToLoad; + + explicit JASPConfiguration(QObject *parent = nullptr); + static JASPConfiguration* _instance; + + const QString defaultConfigurationFilename = "conf.toml"; + QString defaultConfigurationFolder; + QString defaultConfigurationPath; +}; + +#endif // JASPCONFIGURATION_H diff --git a/Desktop/gui/jaspConfiguration/jaspconfigurationparser.cpp b/Desktop/gui/jaspConfiguration/jaspconfigurationparser.cpp new file mode 100644 index 0000000000..15845325c9 --- /dev/null +++ b/Desktop/gui/jaspConfiguration/jaspconfigurationparser.cpp @@ -0,0 +1,16 @@ +#include "jaspconfigurationparser.h" +#include "jaspconfigurationtomlparser.h" +#include "log.h" + + +JASPConfigurationParser* JASPConfigurationParser::getParser(const Format format) +{ + //add cases here as more formats/parsers are added + return new JASPConfigurationTOMLParser(); + +} + +JASPConfigurationParser::JASPConfigurationParser() +{ + +} diff --git a/Desktop/gui/jaspConfiguration/jaspconfigurationparser.h b/Desktop/gui/jaspConfiguration/jaspconfigurationparser.h new file mode 100644 index 0000000000..2787fa002f --- /dev/null +++ b/Desktop/gui/jaspConfiguration/jaspconfigurationparser.h @@ -0,0 +1,23 @@ +#ifndef JASPCONFIGURATION_PARSER_H +#define JASPCONFIGURATION_PARSER_H + +#include "jaspconfiguration.h" + +class JASPConfigurationParser +{ + +public: + enum class Format { + TOML + }; + + //factory function + static JASPConfigurationParser* getParser(const Format format); + + virtual bool parse(JASPConfiguration* target, const QString& input) = 0; + +protected: + JASPConfigurationParser(); +}; + +#endif // JASPCONFIGURATION_PARSER_H diff --git a/Desktop/gui/jaspConfiguration/jaspconfigurationtomlparser.cpp b/Desktop/gui/jaspConfiguration/jaspconfigurationtomlparser.cpp new file mode 100644 index 0000000000..3f25bc9e49 --- /dev/null +++ b/Desktop/gui/jaspConfiguration/jaspconfigurationtomlparser.cpp @@ -0,0 +1,157 @@ +#include "jaspconfigurationtomlparser.h" +#include "parserlibs/toml.h" +#include "log.h" +#include "version.h" + +JASPConfigurationTOMLParser::JASPConfigurationTOMLParser() +{ + +} + +bool JASPConfigurationTOMLParser::parse(JASPConfiguration *target, const QString &input) +{ + toml::table tbl; + + try + { + tbl = toml::parse(input.toStdString()); + } + catch (const toml::parse_error& err) + { + Log::log() << "Parsing failed: " << err << std::endl; + } + + //jasp version + std::optional jaspVersion = tbl["JASPVersion"].value(); + if(jaspVersion) + parseVersion(target, jaspVersion.value()); + + //enabled modules + toml::array* modulesToLoad = tbl.get_as("EnabledModules"); + if(modulesToLoad) + modulesToLoad->for_each([&](toml::value& elem) { target->setAdditionalModule(QString(elem.get().c_str())); }); + + //root constants + toml::table* rootConstants = tbl.get_as("Constants"); + if(rootConstants) + parseConstants(target, *rootConstants); + + //modules -> constants, (analyses -> constants & options) + toml::table* modules = tbl.get_as("Modules"); + if(modules) + { + modules->for_each([&](toml::key key, toml::node& node) { + if(node.is_table()) + parseModule(target, key.data(), *node.as_table()); + }); + } + + //startup commands + std::optional cmds = tbl["StartupCommands"].value(); + if(cmds) + target->setStartupCommands(QString(cmds.value().data())); + + return true; +} + +bool JASPConfigurationTOMLParser::parseVersion(JASPConfiguration* target, std::string_view version) +{ + try + { + target->setJASPVersion(Version(std::string(version))); + } + catch(std::runtime_error& e) + { + Log::log() << "Corrupt version:" << e.what() << std::endl; + return false; + } + return true; +} + +bool JASPConfigurationTOMLParser::parseModule(JASPConfiguration *target, const std::string& module, const toml::table &tbl) +{ + //constants + const toml::table* constants = tbl.get_as("Constants"); + if(constants) + parseConstants(target, *constants, module); + + //analyses + const toml::table* analyses = tbl.get_as("Analyses"); + if(analyses) + { + analyses->for_each([&](const toml::key key, const toml::node& node) { + if(node.is_table()) + parseAnalysis(target, module, key.data(), *node.as_table()); + }); + } + return true; +} + +bool JASPConfigurationTOMLParser::parseAnalysis(JASPConfiguration *target, const std::string &module, const std::string &analysis, const toml::table &tbl) +{ + //constants + const toml::table* constants = tbl.get_as("Constants"); + if(constants) + parseConstants(target, *constants, module, analysis); + + //options + const toml::table* options = tbl.get_as("Options"); + if(options) + { + options->for_each([&](const toml::key key, const toml::node& node) { + if(node.is_value()) + { + QVariant res; + if(toQVariant(node, res)) + target->addOption(key.data(), res, false, QString(module.data()), QString(analysis.data())); + } + else if(node.is_table()) + { + const toml::table& option = *node.as_table(); + std::optional lock = option["Lock"].value(); + toml::node_view value = option["Value"]; + QVariant res; + if(value && lock && toQVariant(*value.node(), res)) + target->addOption(key.data(), res, lock.value(), QString(module.data()), QString(analysis.data())); + } + }); + } + return true; + +} + +bool JASPConfigurationTOMLParser::parseConstants(JASPConfiguration *target, const toml::table &tbl, const std::string &module, const std::string& analysis) +{ + tbl.for_each([&](const toml::key key, const toml::node& value) { + QVariant res; + if(toQVariant(value, res)) + target->addConstant(QString(key.data()), res, QString(module.c_str()), QString(analysis.c_str())); + }); + + return true; +} + + +//this can probably be done more elegantly +//might want to expand with dates etc +bool JASPConfigurationTOMLParser::toQVariant(const toml::node &node, QVariant& res) +{ + switch (node.type()) { + case toml::node_type::string: + res.setValue(node.value().value()); + break; + case toml::node_type::integer: + res.setValue(node.value().value()); + break; + case toml::node_type::floating_point: + res.setValue(node.value().value()); + break; + case toml::node_type::boolean: + res.setValue(node.value().value()); + break; + default: + return false; + } + return true; +} + diff --git a/Desktop/gui/jaspConfiguration/jaspconfigurationtomlparser.h b/Desktop/gui/jaspConfiguration/jaspconfigurationtomlparser.h new file mode 100644 index 0000000000..97ddddfd5f --- /dev/null +++ b/Desktop/gui/jaspConfiguration/jaspconfigurationtomlparser.h @@ -0,0 +1,23 @@ +#ifndef JASPCONFIGURATIONTOMLPARSER_H +#define JASPCONFIGURATIONTOMLPARSER_H + +#include "jaspconfigurationparser.h" +#include "parserlibs/toml.h" + +class JASPConfigurationTOMLParser : public JASPConfigurationParser +{ +public: + JASPConfigurationTOMLParser(); + + bool parse(JASPConfiguration* target, const QString& input) override; + +private: + bool parseVersion(JASPConfiguration* target, std::string_view version); + bool parseModule(JASPConfiguration* target, const std::string& module, const toml::table& tbl); + bool parseAnalysis(JASPConfiguration* target, const std::string& module, const std::string& analysis, const toml::table& tbl); + bool parseConstants(JASPConfiguration* target, const toml::table& tbl, const std::string &module = "", const std::string& analysis = ""); + bool toQVariant(const toml::node& node, QVariant& res); + +}; + +#endif // JASPCONFIGURATIONTOMLPARSER_H diff --git a/Desktop/gui/jaspConfiguration/parserlibs/toml.h b/Desktop/gui/jaspConfiguration/parserlibs/toml.h new file mode 100644 index 0000000000..470658ca5a --- /dev/null +++ b/Desktop/gui/jaspConfiguration/parserlibs/toml.h @@ -0,0 +1,17414 @@ +//---------------------------------------------------------------------------------------------------------------------- +// +// toml++ v3.3.0 +// https://github.com/marzer/tomlplusplus +// SPDX-License-Identifier: MIT +// +//---------------------------------------------------------------------------------------------------------------------- +// +// - THIS FILE WAS ASSEMBLED FROM MULTIPLE HEADER FILES BY A SCRIPT - PLEASE DON'T EDIT IT DIRECTLY - +// +// If you wish to submit a contribution to toml++, hooray and thanks! Before you crack on, please be aware that this +// file was assembled from a number of smaller files by a python script, and code contributions should not be made +// against it directly. You should instead make your changes in the relevant source file(s). The file names of the files +// that contributed to this header can be found at the beginnings and ends of the corresponding sections of this file. +// +//---------------------------------------------------------------------------------------------------------------------- +// +// TOML Language Specifications: +// latest: https://github.com/toml-lang/toml/blob/master/README.md +// v1.0.0: https://toml.io/en/v1.0.0 +// v0.5.0: https://toml.io/en/v0.5.0 +// changelog: https://github.com/toml-lang/toml/blob/master/CHANGELOG.md +// +//---------------------------------------------------------------------------------------------------------------------- +// +// MIT License +// +// Copyright (c) Mark Gillard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +// Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//---------------------------------------------------------------------------------------------------------------------- +#ifndef TOMLPLUSPLUS_H +#define TOMLPLUSPLUS_H + +#define INCLUDE_TOMLPLUSPLUS_H // old guard name used pre-v3 + +//******** impl/preprocessor.h *************************************************************************************** + +#ifndef __cplusplus +#error toml++ is a C++ library. +#endif +#ifdef _MSVC_LANG +#define TOML_CPP _MSVC_LANG +#else +#define TOML_CPP __cplusplus +#endif +#if TOML_CPP >= 202002L +#undef TOML_CPP +#define TOML_CPP 20 +#elif TOML_CPP >= 201703L +#undef TOML_CPP +#define TOML_CPP 17 +#else +#if TOML_CPP < 201103L +#error toml++ requires C++17 or higher. For a pre-C++11 TOML library see https://github.com/ToruNiina/Boost.toml +#elif TOML_CPP < 201703L +#error toml++ requires C++17 or higher. For a C++11 TOML library see https://github.com/ToruNiina/toml11 +#endif +#endif + +#define TOML_MAKE_VERSION(major, minor, patch) (((major)*10000) + ((minor)*100) + ((patch))) + +#ifdef __clang__ +#define TOML_CLANG __clang_major__ +#define TOML_CLANG_VERSION TOML_MAKE_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) +#else +#define TOML_CLANG 0 +#endif +#ifdef __INTEL_COMPILER +#define TOML_ICC __INTEL_COMPILER +#ifdef __ICL +#define TOML_ICC_CL TOML_ICC +#else +#define TOML_ICC_CL 0 +#endif +#else +#define TOML_ICC 0 +#define TOML_ICC_CL 0 +#endif +#if defined(_MSC_VER) && !TOML_CLANG && !TOML_ICC +#define TOML_MSVC _MSC_VER +#else +#define TOML_MSVC 0 +#endif +#if defined(__GNUC__) && !TOML_CLANG && !TOML_ICC +#define TOML_GCC __GNUC__ +#else +#define TOML_GCC 0 +#endif +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__CYGWIN__) +#define TOML_WINDOWS 1 +#else +#define TOML_WINDOWS 0 +#endif +#if defined(DOXYGEN) || defined(__DOXYGEN__) || defined(__POXY__) || defined(__poxy__) +#define TOML_DOXYGEN 1 +#else +#define TOML_DOXYGEN 0 +#endif +#ifdef __INTELLISENSE__ +#define TOML_INTELLISENSE 1 +#else +#define TOML_INTELLISENSE 0 +#endif + +// special handling for apple clang; see: +// - https://github.com/marzer/tomlplusplus/issues/189 +// - https://en.wikipedia.org/wiki/Xcode +// - https://stackoverflow.com/questions/19387043/how-can-i-reliably-detect-the-version-of-clang-at-preprocessing-time +#if TOML_CLANG && defined(__apple_build_version__) +#undef TOML_CLANG +#if TOML_CLANG_VERSION >= TOML_MAKE_VERSION(14, 0, 0) +#define TOML_CLANG 14 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(13, 1, 6) +#define TOML_CLANG 13 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(13, 0, 0) +#define TOML_CLANG 12 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(12, 0, 5) +#define TOML_CLANG 11 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(12, 0, 0) +#define TOML_CLANG 10 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(11, 0, 3) +#define TOML_CLANG 9 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(11, 0, 0) +#define TOML_CLANG 8 +#elif TOML_CLANG_VERSION >= TOML_MAKE_VERSION(10, 0, 1) +#define TOML_CLANG 7 +#else +#define TOML_CLANG 6 // not strictly correct but doesn't matter below this +#endif +#endif + +// IA64 +#if defined(__ia64__) || defined(__ia64) || defined(_IA64) || defined(__IA64__) || defined(_M_IA64) +#define TOML_ARCH_ITANIUM 1 +#else +#define TOML_ARCH_ITANIUM 0 +#endif + +// AMD64 +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_AMD64) +#define TOML_ARCH_AMD64 1 +#else +#define TOML_ARCH_AMD64 0 +#endif + +// 32-bit x86 +#if defined(__i386__) || defined(_M_IX86) +#define TOML_ARCH_X86 1 +#else +#define TOML_ARCH_X86 0 +#endif + +// ARM +#if defined(__aarch64__) || defined(__ARM_ARCH_ISA_A64) || defined(_M_ARM64) || defined(__ARM_64BIT_STATE) \ + || defined(_M_ARM64EC) +#define TOML_ARCH_ARM32 0 +#define TOML_ARCH_ARM64 1 +#define TOML_ARCH_ARM 1 +#elif defined(__arm__) || defined(_M_ARM) || defined(__ARM_32BIT_STATE) +#define TOML_ARCH_ARM32 1 +#define TOML_ARCH_ARM64 0 +#define TOML_ARCH_ARM 1 +#else +#define TOML_ARCH_ARM32 0 +#define TOML_ARCH_ARM64 0 +#define TOML_ARCH_ARM 0 +#endif + +// TOML_HAS_INCLUDE +#ifdef __has_include +#define TOML_HAS_INCLUDE(header) __has_include(header) +#else +#define TOML_HAS_INCLUDE(header) 0 +#endif + +#ifdef __has_builtin +#define TOML_HAS_BUILTIN(name) __has_builtin(name) +#else +#define TOML_HAS_BUILTIN(name) 0 +#endif + +// TOML_HAS_FEATURE +#ifdef __has_feature +#define TOML_HAS_FEATURE(name) __has_feature(name) +#else +#define TOML_HAS_FEATURE(name) 0 +#endif + +// TOML_HAS_ATTR +#ifdef __has_attribute +#define TOML_HAS_ATTR(attr) __has_attribute(attr) +#else +#define TOML_HAS_ATTR(attr) 0 +#endif + +// TOML_HAS_CPP_ATTR +#ifdef __has_cpp_attribute +#define TOML_HAS_CPP_ATTR(attr) __has_cpp_attribute(attr) +#else +#define TOML_HAS_CPP_ATTR(attr) 0 +#endif + +// TOML_COMPILER_HAS_EXCEPTIONS +#if defined(__EXCEPTIONS) || defined(_CPPUNWIND) || defined(__cpp_exceptions) +#define TOML_COMPILER_HAS_EXCEPTIONS 1 +#else +#define TOML_COMPILER_HAS_EXCEPTIONS 0 +#endif + +// TOML_COMPILER_HAS_RTTI +#if defined(_CPPRTTI) || defined(__GXX_RTTI) || TOML_HAS_FEATURE(cxx_rtti) +#define TOML_COMPILER_HAS_RTTI 1 +#else +#define TOML_COMPILER_HAS_RTTI 0 +#endif + +// TOML_ATTR (gnu attributes) +#if TOML_CLANG || TOML_GCC || defined(__GNUC__) +#define TOML_ATTR(...) __attribute__((__VA_ARGS__)) +#else +#define TOML_ATTR(...) +#endif + +// TOML_DECLSPEC (msvc attributes) +#ifdef _MSC_VER +#define TOML_DECLSPEC(...) __declspec(__VA_ARGS__) +#else +#define TOML_DECLSPEC(...) +#endif + +// TOML_CONCAT +#define TOML_CONCAT_1(x, y) x##y +#define TOML_CONCAT(x, y) TOML_CONCAT_1(x, y) + +// TOML_MAKE_STRING +#define TOML_MAKE_STRING_1(s) #s +#define TOML_MAKE_STRING(s) TOML_MAKE_STRING_1(s) + +// TOML_PRAGMA_XXXX (compiler-specific pragmas) +#if TOML_CLANG +#define TOML_PRAGMA_CLANG(decl) _Pragma(TOML_MAKE_STRING(clang decl)) +#else +#define TOML_PRAGMA_CLANG(decl) +#endif +#if TOML_CLANG >= 9 +#define TOML_PRAGMA_CLANG_GE_9(decl) TOML_PRAGMA_CLANG(decl) +#else +#define TOML_PRAGMA_CLANG_GE_9(decl) +#endif +#if TOML_CLANG >= 10 +#define TOML_PRAGMA_CLANG_GE_10(decl) TOML_PRAGMA_CLANG(decl) +#else +#define TOML_PRAGMA_CLANG_GE_10(decl) +#endif +#if TOML_CLANG >= 11 +#define TOML_PRAGMA_CLANG_GE_11(decl) TOML_PRAGMA_CLANG(decl) +#else +#define TOML_PRAGMA_CLANG_GE_11(decl) +#endif +#if TOML_GCC +#define TOML_PRAGMA_GCC(decl) _Pragma(TOML_MAKE_STRING(GCC decl)) +#else +#define TOML_PRAGMA_GCC(decl) +#endif +#if TOML_MSVC +#define TOML_PRAGMA_MSVC(...) __pragma(__VA_ARGS__) +#else +#define TOML_PRAGMA_MSVC(...) +#endif +#if TOML_ICC +#define TOML_PRAGMA_ICC(...) __pragma(__VA_ARGS__) +#else +#define TOML_PRAGMA_ICC(...) +#endif + +// TOML_ALWAYS_INLINE +#ifdef _MSC_VER +#define TOML_ALWAYS_INLINE __forceinline +#elif TOML_GCC || TOML_CLANG || TOML_HAS_ATTR(__always_inline__) +#define TOML_ALWAYS_INLINE \ + TOML_ATTR(__always_inline__) \ + inline +#else +#define TOML_ALWAYS_INLINE inline +#endif + +// TOML_NEVER_INLINE +#ifdef _MSC_VER +#define TOML_NEVER_INLINE TOML_DECLSPEC(noinline) +#elif TOML_GCC || TOML_CLANG || TOML_HAS_ATTR(__noinline__) +#define TOML_NEVER_INLINE TOML_ATTR(__noinline__) +#else +#define TOML_NEVER_INLINE +#endif + +// MSVC attributes +#define TOML_ABSTRACT_INTERFACE TOML_DECLSPEC(novtable) +#define TOML_EMPTY_BASES TOML_DECLSPEC(empty_bases) + +// TOML_TRIVIAL_ABI +#if TOML_CLANG || TOML_HAS_ATTR(__trivial_abi__) +#define TOML_TRIVIAL_ABI TOML_ATTR(__trivial_abi__) +#else +#define TOML_TRIVIAL_ABI +#endif + +// TOML_NODISCARD +#if TOML_CPP >= 17 && TOML_HAS_CPP_ATTR(nodiscard) >= 201603 +#define TOML_NODISCARD [[nodiscard]] +#elif TOML_CLANG || TOML_GCC || TOML_HAS_ATTR(__warn_unused_result__) +#define TOML_NODISCARD TOML_ATTR(__warn_unused_result__) +#else +#define TOML_NODISCARD +#endif + +// TOML_NODISCARD_CTOR +#if TOML_CPP >= 17 && TOML_HAS_CPP_ATTR(nodiscard) >= 201907 +#define TOML_NODISCARD_CTOR [[nodiscard]] +#else +#define TOML_NODISCARD_CTOR +#endif + +// pure + const +// clang-format off +#ifdef NDEBUG + #define TOML_PURE TOML_DECLSPEC(noalias) TOML_ATTR(__pure__) + #define TOML_CONST TOML_DECLSPEC(noalias) TOML_ATTR(__const__) + #define TOML_PURE_GETTER TOML_NODISCARD TOML_PURE + #define TOML_CONST_GETTER TOML_NODISCARD TOML_CONST + #define TOML_PURE_INLINE_GETTER TOML_NODISCARD TOML_ALWAYS_INLINE TOML_PURE + #define TOML_CONST_INLINE_GETTER TOML_NODISCARD TOML_ALWAYS_INLINE TOML_CONST +#else + #define TOML_PURE + #define TOML_CONST + #define TOML_PURE_GETTER TOML_NODISCARD + #define TOML_CONST_GETTER TOML_NODISCARD + #define TOML_PURE_INLINE_GETTER TOML_NODISCARD TOML_ALWAYS_INLINE + #define TOML_CONST_INLINE_GETTER TOML_NODISCARD TOML_ALWAYS_INLINE +#endif +// clang-format on + +// TOML_ASSUME +#ifdef _MSC_VER +#define TOML_ASSUME(...) __assume(__VA_ARGS__) +#elif TOML_ICC || TOML_CLANG || TOML_HAS_BUILTIN(__builtin_assume) +#define TOML_ASSUME(...) __builtin_assume(__VA_ARGS__) +#else +#define TOML_ASSUME(...) static_assert(true) +#endif + +// TOML_UNREACHABLE +#ifdef _MSC_VER +#define TOML_UNREACHABLE __assume(0) +#elif TOML_ICC || TOML_CLANG || TOML_GCC || TOML_HAS_BUILTIN(__builtin_unreachable) +#define TOML_UNREACHABLE __builtin_unreachable() +#else +#define TOML_UNREACHABLE static_assert(true) +#endif + +// TOML_LIKELY +#if TOML_CPP >= 20 && TOML_HAS_CPP_ATTR(likely) >= 201803 +#define TOML_LIKELY(...) (__VA_ARGS__) [[likely]] +#define TOML_LIKELY_CASE [[likely]] +#elif TOML_GCC || TOML_CLANG || TOML_HAS_BUILTIN(__builtin_expect) +#define TOML_LIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 1)) +#else +#define TOML_LIKELY(...) (__VA_ARGS__) +#endif +#ifndef TOML_LIKELY_CASE +#define TOML_LIKELY_CASE +#endif + +// TOML_UNLIKELY +#if TOML_CPP >= 20 && TOML_HAS_CPP_ATTR(unlikely) >= 201803 +#define TOML_UNLIKELY(...) (__VA_ARGS__) [[unlikely]] +#define TOML_UNLIKELY_CASE [[unlikely]] +#elif TOML_GCC || TOML_CLANG || TOML_HAS_BUILTIN(__builtin_expect) +#define TOML_UNLIKELY(...) (__builtin_expect(!!(__VA_ARGS__), 0)) +#else +#define TOML_UNLIKELY(...) (__VA_ARGS__) +#endif +#ifndef TOML_UNLIKELY_CASE +#define TOML_UNLIKELY_CASE +#endif + +// TOML_FLAGS_ENUM +#if TOML_CLANG || TOML_HAS_ATTR(flag_enum) +#define TOML_FLAGS_ENUM __attribute__((flag_enum)) +#else +#define TOML_FLAGS_ENUM +#endif + +// TOML_OPEN_ENUM + TOML_CLOSED_ENUM +#if TOML_CLANG || TOML_HAS_ATTR(enum_extensibility) +#define TOML_OPEN_ENUM __attribute__((enum_extensibility(open))) +#define TOML_CLOSED_ENUM __attribute__((enum_extensibility(closed))) +#else +#define TOML_OPEN_ENUM +#define TOML_CLOSED_ENUM +#endif + +// TOML_OPEN_FLAGS_ENUM + TOML_CLOSED_FLAGS_ENUM +#define TOML_OPEN_FLAGS_ENUM TOML_OPEN_ENUM TOML_FLAGS_ENUM +#define TOML_CLOSED_FLAGS_ENUM TOML_CLOSED_ENUM TOML_FLAGS_ENUM + +// TOML_MAKE_FLAGS +#define TOML_MAKE_FLAGS_2(T, op, linkage) \ + TOML_CONST_INLINE_GETTER \ + linkage constexpr T operator op(T lhs, T rhs) noexcept \ + { \ + using under = std::underlying_type_t; \ + return static_cast(static_cast(lhs) op static_cast(rhs)); \ + } \ + \ + linkage constexpr T& operator TOML_CONCAT(op, =)(T & lhs, T rhs) noexcept \ + { \ + return lhs = (lhs op rhs); \ + } \ + \ + static_assert(true) +#define TOML_MAKE_FLAGS_1(T, linkage) \ + static_assert(std::is_enum_v); \ + \ + TOML_MAKE_FLAGS_2(T, &, linkage); \ + TOML_MAKE_FLAGS_2(T, |, linkage); \ + TOML_MAKE_FLAGS_2(T, ^, linkage); \ + \ + TOML_CONST_INLINE_GETTER \ + linkage constexpr T operator~(T val) noexcept \ + { \ + using under = std::underlying_type_t; \ + return static_cast(~static_cast(val)); \ + } \ + \ + TOML_CONST_INLINE_GETTER \ + linkage constexpr bool operator!(T val) noexcept \ + { \ + using under = std::underlying_type_t; \ + return !static_cast(val); \ + } \ + \ + static_assert(true) +#define TOML_MAKE_FLAGS(T) TOML_MAKE_FLAGS_1(T, ) + +#define TOML_UNUSED(...) static_cast(__VA_ARGS__) + +#define TOML_DELETE_DEFAULTS(T) \ + T(const T&) = delete; \ + T(T&&) = delete; \ + T& operator=(const T&) = delete; \ + T& operator=(T&&) = delete + +#define TOML_ASYMMETRICAL_EQUALITY_OPS(LHS, RHS, ...) \ + __VA_ARGS__ TOML_NODISCARD \ + friend bool operator==(RHS rhs, LHS lhs) noexcept \ + { \ + return lhs == rhs; \ + } \ + __VA_ARGS__ TOML_NODISCARD \ + friend bool operator!=(LHS lhs, RHS rhs) noexcept \ + { \ + return !(lhs == rhs); \ + } \ + __VA_ARGS__ TOML_NODISCARD \ + friend bool operator!=(RHS rhs, LHS lhs) noexcept \ + { \ + return !(lhs == rhs); \ + } \ + static_assert(true) + +#define TOML_EVAL_BOOL_1(T, F) T +#define TOML_EVAL_BOOL_0(T, F) F + +#if !defined(__POXY__) && !defined(POXY_IMPLEMENTATION_DETAIL) +#define POXY_IMPLEMENTATION_DETAIL(...) __VA_ARGS__ +#endif + +// COMPILER-SPECIFIC WARNING MANAGEMENT + +#if TOML_CLANG + +#define TOML_PUSH_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic push) \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wunknown-warning-option") \ + static_assert(true) + +#define TOML_DISABLE_SWITCH_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wswitch") \ + static_assert(true) + +#define TOML_DISABLE_ARITHMETIC_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wfloat-equal") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wdouble-promotion") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wchar-subscripts") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wshift-sign-overflow") \ + static_assert(true) + +#define TOML_DISABLE_SPAM_WARNINGS \ + TOML_PRAGMA_CLANG_GE_9(diagnostic ignored "-Wctad-maybe-unsupported") \ + TOML_PRAGMA_CLANG_GE_10(diagnostic ignored "-Wzero-as-null-pointer-constant") \ + TOML_PRAGMA_CLANG_GE_11(diagnostic ignored "-Wsuggest-destructor-override") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wweak-vtables") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wweak-template-vtables") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wdouble-promotion") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wchar-subscripts") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wmissing-field-initializers") \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Wpadded") \ + static_assert(true) + +#define TOML_POP_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic pop) \ + static_assert(true) + +#define TOML_DISABLE_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic push) \ + TOML_PRAGMA_CLANG(diagnostic ignored "-Weverything") \ + static_assert(true, "") + +#define TOML_ENABLE_WARNINGS \ + TOML_PRAGMA_CLANG(diagnostic pop) \ + static_assert(true) + +#define TOML_SIMPLE_STATIC_ASSERT_MESSAGES 1 + +#elif TOML_MSVC + +#define TOML_PUSH_WARNINGS \ + __pragma(warning(push)) \ + static_assert(true) + +#if TOML_HAS_INCLUDE() +#pragma warning(push, 0) +#include +#pragma warning(pop) +#define TOML_DISABLE_CODE_ANALYSIS_WARNINGS \ + __pragma(warning(disable : ALL_CODE_ANALYSIS_WARNINGS)) \ + static_assert(true) +#else +#define TOML_DISABLE_CODE_ANALYSIS_WARNINGS static_assert(true) +#endif + +#define TOML_DISABLE_SWITCH_WARNINGS \ + __pragma(warning(disable : 4061)) \ + __pragma(warning(disable : 4062)) \ + __pragma(warning(disable : 4063)) \ + __pragma(warning(disable : 5262)) /* switch-case implicit fallthrough (false-positive) */ \ + __pragma(warning(disable : 26819)) /* cg: unannotated fallthrough */ \ + static_assert(true) + +#define TOML_DISABLE_SPAM_WARNINGS \ + __pragma(warning(disable : 4127)) /* conditional expr is constant */ \ + __pragma(warning(disable : 4324)) /* structure was padded due to alignment specifier */ \ + __pragma(warning(disable : 4348)) \ + __pragma(warning(disable : 4464)) /* relative include path contains '..' */ \ + __pragma(warning(disable : 4505)) /* unreferenced local function removed */ \ + __pragma(warning(disable : 4514)) /* unreferenced inline function has been removed */ \ + __pragma(warning(disable : 4582)) /* constructor is not implicitly called */ \ + __pragma(warning(disable : 4619)) /* there is no warning number 'XXXX' */ \ + __pragma(warning(disable : 4623)) /* default constructor was implicitly defined as deleted */ \ + __pragma(warning(disable : 4625)) /* copy constructor was implicitly defined as deleted */ \ + __pragma(warning(disable : 4626)) /* assignment operator was implicitly defined as deleted */ \ + __pragma(warning(disable : 4710)) /* function not inlined */ \ + __pragma(warning(disable : 4711)) /* function selected for automatic expansion */ \ + __pragma(warning(disable : 4820)) /* N bytes padding added */ \ + __pragma(warning(disable : 4946)) /* reinterpret_cast used between related classes */ \ + __pragma(warning(disable : 5026)) /* move constructor was implicitly defined as deleted */ \ + __pragma(warning(disable : 5027)) /* move assignment operator was implicitly defined as deleted */ \ + __pragma(warning(disable : 5039)) /* potentially throwing function passed to 'extern "C"' function */ \ + __pragma(warning(disable : 5045)) /* Compiler will insert Spectre mitigation */ \ + __pragma(warning(disable : 5264)) /* const variable is not used (false-positive) */ \ + __pragma(warning(disable : 26451)) \ + __pragma(warning(disable : 26490)) \ + __pragma(warning(disable : 26495)) \ + __pragma(warning(disable : 26812)) \ + __pragma(warning(disable : 26819)) \ + static_assert(true) + +#define TOML_DISABLE_ARITHMETIC_WARNINGS \ + __pragma(warning(disable : 4365)) /* argument signed/unsigned mismatch */ \ + __pragma(warning(disable : 4738)) /* storing 32-bit float result in memory */ \ + __pragma(warning(disable : 5219)) /* implicit conversion from integral to float */ \ + static_assert(true) + +#define TOML_POP_WARNINGS \ + __pragma(warning(pop)) \ + static_assert(true) + +#define TOML_DISABLE_WARNINGS \ + __pragma(warning(push, 0)) \ + __pragma(warning(disable : 4348)) \ + __pragma(warning(disable : 4668)) \ + __pragma(warning(disable : 5105)) \ + __pragma(warning(disable : 5264)) \ + TOML_DISABLE_CODE_ANALYSIS_WARNINGS; \ + TOML_DISABLE_SWITCH_WARNINGS; \ + TOML_DISABLE_SPAM_WARNINGS; \ + TOML_DISABLE_ARITHMETIC_WARNINGS; \ + static_assert(true) + +#define TOML_ENABLE_WARNINGS TOML_POP_WARNINGS + +#elif TOML_ICC + +#define TOML_PUSH_WARNINGS \ + __pragma(warning(push)) \ + static_assert(true) + +#define TOML_DISABLE_SPAM_WARNINGS \ + __pragma(warning(disable : 82)) /* storage class is not first */ \ + __pragma(warning(disable : 111)) /* statement unreachable (false-positive) */ \ + __pragma(warning(disable : 869)) /* unreferenced parameter */ \ + __pragma(warning(disable : 1011)) /* missing return (false-positive) */ \ + __pragma(warning(disable : 2261)) /* assume expr side-effects discarded */ \ + static_assert(true) + +#define TOML_POP_WARNINGS \ + __pragma(warning(pop)) \ + static_assert(true) + +#define TOML_DISABLE_WARNINGS \ + __pragma(warning(push, 0)) \ + TOML_DISABLE_SPAM_WARNINGS + +#define TOML_ENABLE_WARNINGS \ + __pragma(warning(pop)) \ + static_assert(true) + +#elif TOML_GCC + +#define TOML_PUSH_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic push) \ + static_assert(true) + +#define TOML_DISABLE_SWITCH_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wswitch") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wswitch-enum") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wswitch-default") \ + static_assert(true) + +#define TOML_DISABLE_ARITHMETIC_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wfloat-equal") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wsign-conversion") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wchar-subscripts") \ + static_assert(true) + +#define TOML_DISABLE_SUGGEST_ATTR_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wsuggest-attribute=const") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wsuggest-attribute=pure") \ + static_assert(true) + +#define TOML_DISABLE_SPAM_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wpadded") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wcast-align") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wcomment") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wtype-limits") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wuseless-cast") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wchar-subscripts") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wsubobject-linkage") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wmissing-field-initializers") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wmaybe-uninitialized") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wnoexcept") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wnull-dereference") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wduplicated-branches") \ + static_assert(true) + +#define TOML_POP_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic pop) \ + static_assert(true) + +#define TOML_DISABLE_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic push) \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wall") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wextra") \ + TOML_PRAGMA_GCC(diagnostic ignored "-Wpedantic") \ + TOML_DISABLE_SWITCH_WARNINGS; \ + TOML_DISABLE_ARITHMETIC_WARNINGS; \ + TOML_DISABLE_SUGGEST_ATTR_WARNINGS; \ + TOML_DISABLE_SPAM_WARNINGS; \ + static_assert(true) + +#define TOML_ENABLE_WARNINGS \ + TOML_PRAGMA_GCC(diagnostic pop) \ + static_assert(true) + +#endif + +#ifndef TOML_PUSH_WARNINGS +#define TOML_PUSH_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_CODE_ANALYSIS_WARNINGS +#define TOML_DISABLE_CODE_ANALYSIS_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_SWITCH_WARNINGS +#define TOML_DISABLE_SWITCH_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_SUGGEST_ATTR_WARNINGS +#define TOML_DISABLE_SUGGEST_ATTR_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_SPAM_WARNINGS +#define TOML_DISABLE_SPAM_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_ARITHMETIC_WARNINGS +#define TOML_DISABLE_ARITHMETIC_WARNINGS static_assert(true) +#endif +#ifndef TOML_POP_WARNINGS +#define TOML_POP_WARNINGS static_assert(true) +#endif +#ifndef TOML_DISABLE_WARNINGS +#define TOML_DISABLE_WARNINGS static_assert(true) +#endif +#ifndef TOML_ENABLE_WARNINGS +#define TOML_ENABLE_WARNINGS static_assert(true) +#endif +#ifndef TOML_SIMPLE_STATIC_ASSERT_MESSAGES +#define TOML_SIMPLE_STATIC_ASSERT_MESSAGES 0 +#endif + +#ifdef TOML_CONFIG_HEADER +#include TOML_CONFIG_HEADER +#endif + +// is the library being built as a shared lib/dll using meson and friends? +#ifndef TOML_SHARED_LIB +#define TOML_SHARED_LIB 0 +#endif + +// header-only mode +#if !defined(TOML_HEADER_ONLY) && defined(TOML_ALL_INLINE) // was TOML_ALL_INLINE pre-2.0 +#define TOML_HEADER_ONLY TOML_ALL_INLINE +#endif +#if !defined(TOML_HEADER_ONLY) || (defined(TOML_HEADER_ONLY) && TOML_HEADER_ONLY) || TOML_INTELLISENSE +#undef TOML_HEADER_ONLY +#define TOML_HEADER_ONLY 1 +#endif +#if TOML_DOXYGEN || TOML_SHARED_LIB +#undef TOML_HEADER_ONLY +#define TOML_HEADER_ONLY 0 +#endif + +// internal implementation switch +#if defined(TOML_IMPLEMENTATION) || TOML_HEADER_ONLY +#undef TOML_IMPLEMENTATION +#define TOML_IMPLEMENTATION 1 +#else +#define TOML_IMPLEMENTATION 0 +#endif + +// dll/shared lib function exports (legacy - TOML_API was the old name for this setting) +#if !defined(TOML_EXPORTED_MEMBER_FUNCTION) && !defined(TOML_EXPORTED_STATIC_FUNCTION) \ + && !defined(TOML_EXPORTED_FREE_FUNCTION) && !defined(TOML_EXPORTED_CLASS) && defined(TOML_API) +#define TOML_EXPORTED_MEMBER_FUNCTION TOML_API +#define TOML_EXPORTED_STATIC_FUNCTION TOML_API +#define TOML_EXPORTED_FREE_FUNCTION TOML_API +#endif + +// dll/shared lib exports +#if TOML_SHARED_LIB +#undef TOML_API +#undef TOML_EXPORTED_CLASS +#undef TOML_EXPORTED_MEMBER_FUNCTION +#undef TOML_EXPORTED_STATIC_FUNCTION +#undef TOML_EXPORTED_FREE_FUNCTION +#if TOML_WINDOWS +#if TOML_IMPLEMENTATION +#define TOML_EXPORTED_CLASS __declspec(dllexport) +#define TOML_EXPORTED_FREE_FUNCTION __declspec(dllexport) +#else +#define TOML_EXPORTED_CLASS __declspec(dllimport) +#define TOML_EXPORTED_FREE_FUNCTION __declspec(dllimport) +#endif +#ifndef TOML_CALLCONV +#define TOML_CALLCONV __cdecl +#endif +#elif defined(__GNUC__) && __GNUC__ >= 4 +#define TOML_EXPORTED_CLASS __attribute__((visibility("default"))) +#define TOML_EXPORTED_MEMBER_FUNCTION __attribute__((visibility("default"))) +#define TOML_EXPORTED_STATIC_FUNCTION __attribute__((visibility("default"))) +#define TOML_EXPORTED_FREE_FUNCTION __attribute__((visibility("default"))) +#endif +#endif +#ifndef TOML_EXPORTED_CLASS +#define TOML_EXPORTED_CLASS +#endif +#ifndef TOML_EXPORTED_MEMBER_FUNCTION +#define TOML_EXPORTED_MEMBER_FUNCTION +#endif +#ifndef TOML_EXPORTED_STATIC_FUNCTION +#define TOML_EXPORTED_STATIC_FUNCTION +#endif +#ifndef TOML_EXPORTED_FREE_FUNCTION +#define TOML_EXPORTED_FREE_FUNCTION +#endif + +// experimental language features +#if !defined(TOML_ENABLE_UNRELEASED_FEATURES) && defined(TOML_UNRELEASED_FEATURES) // was TOML_UNRELEASED_FEATURES + // pre-3.0 +#define TOML_ENABLE_UNRELEASED_FEATURES TOML_UNRELEASED_FEATURES +#endif +#if (defined(TOML_ENABLE_UNRELEASED_FEATURES) && TOML_ENABLE_UNRELEASED_FEATURES) || TOML_INTELLISENSE +#undef TOML_ENABLE_UNRELEASED_FEATURES +#define TOML_ENABLE_UNRELEASED_FEATURES 1 +#endif +#ifndef TOML_ENABLE_UNRELEASED_FEATURES +#define TOML_ENABLE_UNRELEASED_FEATURES 0 +#endif + +// parser +#if !defined(TOML_ENABLE_PARSER) && defined(TOML_PARSER) // was TOML_PARSER pre-3.0 +#define TOML_ENABLE_PARSER TOML_PARSER +#endif +#if !defined(TOML_ENABLE_PARSER) || (defined(TOML_ENABLE_PARSER) && TOML_ENABLE_PARSER) || TOML_INTELLISENSE +#undef TOML_ENABLE_PARSER +#define TOML_ENABLE_PARSER 1 +#endif + +// formatters +#if !defined(TOML_ENABLE_FORMATTERS) || (defined(TOML_ENABLE_FORMATTERS) && TOML_ENABLE_FORMATTERS) || TOML_INTELLISENSE +#undef TOML_ENABLE_FORMATTERS +#define TOML_ENABLE_FORMATTERS 1 +#endif + +// SIMD +#if !defined(TOML_ENABLE_SIMD) || (defined(TOML_ENABLE_SIMD) && TOML_ENABLE_SIMD) || TOML_INTELLISENSE +#undef TOML_ENABLE_SIMD +#define TOML_ENABLE_SIMD 1 +#endif + +// windows compat +#if !defined(TOML_ENABLE_WINDOWS_COMPAT) && defined(TOML_WINDOWS_COMPAT) // was TOML_WINDOWS_COMPAT pre-3.0 +#define TOML_ENABLE_WINDOWS_COMPAT TOML_WINDOWS_COMPAT +#endif +#if !defined(TOML_ENABLE_WINDOWS_COMPAT) || (defined(TOML_ENABLE_WINDOWS_COMPAT) && TOML_ENABLE_WINDOWS_COMPAT) \ + || TOML_INTELLISENSE +#undef TOML_ENABLE_WINDOWS_COMPAT +#define TOML_ENABLE_WINDOWS_COMPAT 1 +#endif + +#if !TOML_WINDOWS +#undef TOML_ENABLE_WINDOWS_COMPAT +#define TOML_ENABLE_WINDOWS_COMPAT 0 +#endif + +#ifndef TOML_INCLUDE_WINDOWS_H +#define TOML_INCLUDE_WINDOWS_H 0 +#endif + +// custom optional +#ifdef TOML_OPTIONAL_TYPE +#define TOML_HAS_CUSTOM_OPTIONAL_TYPE 1 +#else +#define TOML_HAS_CUSTOM_OPTIONAL_TYPE 0 +#endif + +// exceptions (library use) +#if TOML_COMPILER_HAS_EXCEPTIONS +#if !defined(TOML_EXCEPTIONS) || (defined(TOML_EXCEPTIONS) && TOML_EXCEPTIONS) +#undef TOML_EXCEPTIONS +#define TOML_EXCEPTIONS 1 +#endif +#else +#if defined(TOML_EXCEPTIONS) && TOML_EXCEPTIONS +#error TOML_EXCEPTIONS was explicitly enabled but exceptions are disabled/unsupported by the compiler. +#endif +#undef TOML_EXCEPTIONS +#define TOML_EXCEPTIONS 0 +#endif + +// calling convention for static/free/friend functions +#ifndef TOML_CALLCONV +#define TOML_CALLCONV +#endif + +#ifndef TOML_UNDEF_MACROS +#define TOML_UNDEF_MACROS 1 +#endif + +#ifndef TOML_MAX_NESTED_VALUES +#define TOML_MAX_NESTED_VALUES 256 +// this refers to the depth of nested values, e.g. inline tables and arrays. +// 256 is crazy high! if you're hitting this limit with real input, TOML is probably the wrong tool for the job... +#endif + +#ifdef TOML_CHAR_8_STRINGS +#if TOML_CHAR_8_STRINGS +#error TOML_CHAR_8_STRINGS was removed in toml++ 2.0.0; all value setters and getters now work with char8_t strings implicitly. +#endif +#endif + +#ifdef TOML_LARGE_FILES +#if !TOML_LARGE_FILES +#error Support for !TOML_LARGE_FILES (i.e. 'small files') was removed in toml++ 3.0.0. +#endif +#endif + +#ifndef TOML_LIFETIME_HOOKS +#define TOML_LIFETIME_HOOKS 0 +#endif + +#ifdef NDEBUG +#undef TOML_ASSERT +#define TOML_ASSERT(expr) static_assert(true) +#endif +#ifndef TOML_ASSERT +#ifndef assert +TOML_DISABLE_WARNINGS; +#include +TOML_ENABLE_WARNINGS; +#endif +#define TOML_ASSERT(expr) assert(expr) +#endif +#ifdef NDEBUG +#define TOML_ASSERT_ASSUME(expr) TOML_ASSUME(expr) +#else +#define TOML_ASSERT_ASSUME(expr) TOML_ASSERT(expr) +#endif + +#ifndef TOML_ENABLE_FLOAT16 +#define TOML_ENABLE_FLOAT16 0 +#endif + +#if !defined(TOML_FLOAT_CHARCONV) && (TOML_GCC || TOML_CLANG || (TOML_ICC && !TOML_ICC_CL)) +// not supported by any version of GCC or Clang as of 26/11/2020 +// not supported by any version of ICC on Linux as of 11/01/2021 +#define TOML_FLOAT_CHARCONV 0 +#endif +#if !defined(TOML_INT_CHARCONV) && (defined(__EMSCRIPTEN__) || defined(__APPLE__)) +// causes link errors on emscripten +// causes Mac OS SDK version errors on some versions of Apple Clang +#define TOML_INT_CHARCONV 0 +#endif +#ifndef TOML_INT_CHARCONV +#define TOML_INT_CHARCONV 1 +#endif +#ifndef TOML_FLOAT_CHARCONV +#define TOML_FLOAT_CHARCONV 1 +#endif +#if (TOML_INT_CHARCONV || TOML_FLOAT_CHARCONV) && !TOML_HAS_INCLUDE() +#undef TOML_INT_CHARCONV +#undef TOML_FLOAT_CHARCONV +#define TOML_INT_CHARCONV 0 +#define TOML_FLOAT_CHARCONV 0 +#endif + +#if defined(__cpp_concepts) && __cpp_concepts >= 201907 +#define TOML_REQUIRES(...) requires(__VA_ARGS__) +#else +#define TOML_REQUIRES(...) +#endif +#define TOML_ENABLE_IF(...) , typename std::enable_if<(__VA_ARGS__), int>::type = 0 +#define TOML_CONSTRAINED_TEMPLATE(condition, ...) \ + template <__VA_ARGS__ TOML_ENABLE_IF(condition)> \ + TOML_REQUIRES(condition) +#define TOML_HIDDEN_CONSTRAINT(condition, ...) TOML_CONSTRAINED_TEMPLATE(condition, __VA_ARGS__) + +#if defined(__SIZEOF_FLOAT128__) && defined(__FLT128_MANT_DIG__) && defined(__LDBL_MANT_DIG__) \ + && __FLT128_MANT_DIG__ > __LDBL_MANT_DIG__ +#define TOML_FLOAT128 __float128 +#endif + +#ifdef __SIZEOF_INT128__ +#define TOML_INT128 __int128_t +#define TOML_UINT128 __uint128_t +#endif + +// clang-format off + +//******** impl/version.h ******************************************************************************************** + +#define TOML_LIB_MAJOR 3 +#define TOML_LIB_MINOR 3 +#define TOML_LIB_PATCH 0 + +#define TOML_LANG_MAJOR 1 +#define TOML_LANG_MINOR 0 +#define TOML_LANG_PATCH 0 + +//******** impl/preprocessor.h *************************************************************************************** + +#define TOML_LIB_SINGLE_HEADER 1 + +#if TOML_ENABLE_UNRELEASED_FEATURES + #define TOML_LANG_EFFECTIVE_VERSION \ + TOML_MAKE_VERSION(TOML_LANG_MAJOR, TOML_LANG_MINOR, TOML_LANG_PATCH+1) +#else + #define TOML_LANG_EFFECTIVE_VERSION \ + TOML_MAKE_VERSION(TOML_LANG_MAJOR, TOML_LANG_MINOR, TOML_LANG_PATCH) +#endif + +#define TOML_LANG_HIGHER_THAN(major, minor, patch) \ + (TOML_LANG_EFFECTIVE_VERSION > TOML_MAKE_VERSION(major, minor, patch)) + +#define TOML_LANG_AT_LEAST(major, minor, patch) \ + (TOML_LANG_EFFECTIVE_VERSION >= TOML_MAKE_VERSION(major, minor, patch)) + +#define TOML_LANG_UNRELEASED \ + TOML_LANG_HIGHER_THAN(TOML_LANG_MAJOR, TOML_LANG_MINOR, TOML_LANG_PATCH) + +#ifndef TOML_ABI_NAMESPACES + #if TOML_DOXYGEN + #define TOML_ABI_NAMESPACES 0 + #else + #define TOML_ABI_NAMESPACES 1 + #endif +#endif +#if TOML_ABI_NAMESPACES + #define TOML_NAMESPACE_START namespace toml { inline namespace TOML_CONCAT(v, TOML_LIB_MAJOR) + #define TOML_NAMESPACE_END } static_assert(true) + #define TOML_NAMESPACE ::toml::TOML_CONCAT(v, TOML_LIB_MAJOR) + #define TOML_ABI_NAMESPACE_START(name) inline namespace name { static_assert(true) + #define TOML_ABI_NAMESPACE_BOOL(cond, T, F) TOML_ABI_NAMESPACE_START(TOML_CONCAT(TOML_EVAL_BOOL_, cond)(T, F)) + #define TOML_ABI_NAMESPACE_END } static_assert(true) +#else + #define TOML_NAMESPACE_START namespace toml + #define TOML_NAMESPACE_END static_assert(true) + #define TOML_NAMESPACE toml + #define TOML_ABI_NAMESPACE_START(...) static_assert(true) + #define TOML_ABI_NAMESPACE_BOOL(...) static_assert(true) + #define TOML_ABI_NAMESPACE_END static_assert(true) +#endif +#define TOML_IMPL_NAMESPACE_START TOML_NAMESPACE_START { namespace impl +#define TOML_IMPL_NAMESPACE_END } TOML_NAMESPACE_END +#if TOML_HEADER_ONLY + #define TOML_ANON_NAMESPACE_START static_assert(TOML_IMPLEMENTATION); TOML_IMPL_NAMESPACE_START + #define TOML_ANON_NAMESPACE_END TOML_IMPL_NAMESPACE_END + #define TOML_ANON_NAMESPACE TOML_NAMESPACE::impl + #define TOML_EXTERNAL_LINKAGE inline + #define TOML_INTERNAL_LINKAGE inline +#else + #define TOML_ANON_NAMESPACE_START static_assert(TOML_IMPLEMENTATION); \ + using namespace toml; \ + namespace + #define TOML_ANON_NAMESPACE_END static_assert(true) + #define TOML_ANON_NAMESPACE + #define TOML_EXTERNAL_LINKAGE + #define TOML_INTERNAL_LINKAGE static +#endif + +// clang-format on + +// clang-format off + +#if TOML_SIMPLE_STATIC_ASSERT_MESSAGES + + #define TOML_SA_NEWLINE " " + #define TOML_SA_LIST_SEP ", " + #define TOML_SA_LIST_BEG " (" + #define TOML_SA_LIST_END ")" + #define TOML_SA_LIST_NEW " " + #define TOML_SA_LIST_NXT ", " + +#else + + #define TOML_SA_NEWLINE "\n| " + #define TOML_SA_LIST_SEP TOML_SA_NEWLINE " - " + #define TOML_SA_LIST_BEG TOML_SA_LIST_SEP + #define TOML_SA_LIST_END + #define TOML_SA_LIST_NEW TOML_SA_NEWLINE TOML_SA_NEWLINE + #define TOML_SA_LIST_NXT TOML_SA_LIST_NEW + +#endif + +#define TOML_SA_NATIVE_VALUE_TYPE_LIST \ + TOML_SA_LIST_BEG "std::string" \ + TOML_SA_LIST_SEP "int64_t" \ + TOML_SA_LIST_SEP "double" \ + TOML_SA_LIST_SEP "bool" \ + TOML_SA_LIST_SEP "toml::date" \ + TOML_SA_LIST_SEP "toml::time" \ + TOML_SA_LIST_SEP "toml::date_time" \ + TOML_SA_LIST_END + +#define TOML_SA_NODE_TYPE_LIST \ + TOML_SA_LIST_BEG "toml::table" \ + TOML_SA_LIST_SEP "toml::array" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_SEP "toml::value" \ + TOML_SA_LIST_END + +#define TOML_SA_UNWRAPPED_NODE_TYPE_LIST \ + TOML_SA_LIST_NEW "A native TOML value type" \ + TOML_SA_NATIVE_VALUE_TYPE_LIST \ + \ + TOML_SA_LIST_NXT "A TOML node type" \ + TOML_SA_NODE_TYPE_LIST + +// clang-format on + +TOML_PUSH_WARNINGS; +TOML_DISABLE_SPAM_WARNINGS; +TOML_DISABLE_SWITCH_WARNINGS; +TOML_DISABLE_SUGGEST_ATTR_WARNINGS; + +// misc warning false-positives +#if TOML_MSVC +#pragma warning(disable : 5031) // #pragma warning(pop): likely mismatch +#if TOML_SHARED_LIB +#pragma warning(disable : 4251) // dll exports for std lib types +#endif +#elif TOML_CLANG +TOML_PRAGMA_CLANG(diagnostic ignored "-Wheader-hygiene") +#if TOML_CLANG >= 12 +TOML_PRAGMA_CLANG(diagnostic ignored "-Wc++20-extensions") +#endif +#if TOML_CLANG == 13 +TOML_PRAGMA_CLANG(diagnostic ignored "-Wreserved-identifier") +#endif +#endif + +//******** impl/std_new.h ******************************************************************************************** + +TOML_DISABLE_WARNINGS; +#include +TOML_ENABLE_WARNINGS; + +#if (!defined(__apple_build_version__) && TOML_CLANG >= 8) || TOML_GCC >= 7 || TOML_ICC >= 1910 || TOML_MSVC >= 1914 +#define TOML_LAUNDER(x) __builtin_launder(x) +#elif defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606 +#define TOML_LAUNDER(x) std::launder(x) +#else +#define TOML_LAUNDER(x) x +#endif + +//******** impl/std_string.h ***************************************************************************************** + +TOML_DISABLE_WARNINGS; +#include +#include +TOML_ENABLE_WARNINGS; + +#if TOML_DOXYGEN \ + || (defined(__cpp_char8_t) && __cpp_char8_t >= 201811 && defined(__cpp_lib_char8_t) \ + && __cpp_lib_char8_t >= 201907) +#define TOML_HAS_CHAR8 1 +#else +#define TOML_HAS_CHAR8 0 +#endif + +namespace toml // non-abi namespace; this is not an error +{ + using namespace std::string_literals; + using namespace std::string_view_literals; +} + +#if TOML_ENABLE_WINDOWS_COMPAT + +TOML_IMPL_NAMESPACE_START +{ + TOML_NODISCARD + TOML_EXPORTED_FREE_FUNCTION + std::string narrow(std::wstring_view); + + TOML_NODISCARD + TOML_EXPORTED_FREE_FUNCTION + std::wstring widen(std::string_view); + +#if TOML_HAS_CHAR8 + + TOML_NODISCARD + TOML_EXPORTED_FREE_FUNCTION + std::wstring widen(std::u8string_view); + +#endif +} +TOML_IMPL_NAMESPACE_END; + +#endif // TOML_ENABLE_WINDOWS_COMPAT + +//******** impl/std_optional.h *************************************************************************************** + +TOML_DISABLE_WARNINGS; +#if !TOML_HAS_CUSTOM_OPTIONAL_TYPE +#include +#endif +TOML_ENABLE_WARNINGS; + +TOML_NAMESPACE_START +{ +#if TOML_HAS_CUSTOM_OPTIONAL_TYPE + + template + using optional = TOML_OPTIONAL_TYPE; + +#else + + template + using optional = std::optional; + +#endif +} +TOML_NAMESPACE_END; + +//******** impl/forward_declarations.h ******************************************************************************* + +TOML_DISABLE_WARNINGS; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +TOML_ENABLE_WARNINGS; +TOML_PUSH_WARNINGS; +#ifdef _MSC_VER +#ifndef __clang__ +#pragma inline_recursion(on) +#endif +#pragma push_macro("min") +#pragma push_macro("max") +#undef min +#undef max +#endif + +#ifndef TOML_DISABLE_ENVIRONMENT_CHECKS +#define TOML_ENV_MESSAGE \ + "If you're seeing this error it's because you're building toml++ for an environment that doesn't conform to " \ + "one of the 'ground truths' assumed by the library. Essentially this just means that I don't have the " \ + "resources to test on more platforms, but I wish I did! You can try disabling the checks by defining " \ + "TOML_DISABLE_ENVIRONMENT_CHECKS, but your mileage may vary. Please consider filing an issue at " \ + "https://github.com/marzer/tomlplusplus/issues to help me improve support for your target environment. " \ + "Thanks!" + +static_assert(CHAR_BIT == 8, TOML_ENV_MESSAGE); +static_assert(FLT_RADIX == 2, TOML_ENV_MESSAGE); +static_assert('A' == 65, TOML_ENV_MESSAGE); +static_assert(sizeof(double) == 8, TOML_ENV_MESSAGE); +static_assert(std::numeric_limits::is_iec559, TOML_ENV_MESSAGE); +static_assert(std::numeric_limits::digits == 53, TOML_ENV_MESSAGE); +static_assert(std::numeric_limits::digits10 == 15, TOML_ENV_MESSAGE); + +#undef TOML_ENV_MESSAGE +#endif // !TOML_DISABLE_ENVIRONMENT_CHECKS + +// undocumented forward declarations are hidden from doxygen because they fuck it up =/ + +namespace toml // non-abi namespace; this is not an error +{ + using ::std::size_t; + using ::std::intptr_t; + using ::std::uintptr_t; + using ::std::ptrdiff_t; + using ::std::nullptr_t; + using ::std::int8_t; + using ::std::int16_t; + using ::std::int32_t; + using ::std::int64_t; + using ::std::uint8_t; + using ::std::uint16_t; + using ::std::uint32_t; + using ::std::uint64_t; + using ::std::uint_least32_t; + using ::std::uint_least64_t; +} + +TOML_NAMESPACE_START +{ + struct date; + struct time; + struct time_offset; + + TOML_ABI_NAMESPACE_BOOL(TOML_HAS_CUSTOM_OPTIONAL_TYPE, custopt, stdopt); + struct date_time; + TOML_ABI_NAMESPACE_END; + + struct source_position; + struct source_region; + + class node; + template + class node_view; + + class key; + class array; + class table; + template + class value; + + class path; + + class toml_formatter; + class json_formatter; + class yaml_formatter; + + TOML_ABI_NAMESPACE_BOOL(TOML_EXCEPTIONS, ex, noex); +#if TOML_EXCEPTIONS + using parse_result = table; +#else + class parse_result; +#endif + TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS +} +TOML_NAMESPACE_END; + +TOML_IMPL_NAMESPACE_START +{ + using node_ptr = std::unique_ptr; + + TOML_ABI_NAMESPACE_BOOL(TOML_EXCEPTIONS, impl_ex, impl_noex); + class parser; + TOML_ABI_NAMESPACE_END; // TOML_EXCEPTIONS + + // clang-format off + + inline constexpr std::string_view control_char_escapes[] = + { + "\\u0000"sv, + "\\u0001"sv, + "\\u0002"sv, + "\\u0003"sv, + "\\u0004"sv, + "\\u0005"sv, + "\\u0006"sv, + "\\u0007"sv, + "\\b"sv, + "\\t"sv, + "\\n"sv, + "\\u000B"sv, + "\\f"sv, + "\\r"sv, + "\\u000E"sv, + "\\u000F"sv, + "\\u0010"sv, + "\\u0011"sv, + "\\u0012"sv, + "\\u0013"sv, + "\\u0014"sv, + "\\u0015"sv, + "\\u0016"sv, + "\\u0017"sv, + "\\u0018"sv, + "\\u0019"sv, + "\\u001A"sv, + "\\u001B"sv, + "\\u001C"sv, + "\\u001D"sv, + "\\u001E"sv, + "\\u001F"sv, + }; + + inline constexpr std::string_view node_type_friendly_names[] = + { + "none"sv, + "table"sv, + "array"sv, + "string"sv, + "integer"sv, + "floating-point"sv, + "boolean"sv, + "date"sv, + "time"sv, + "date-time"sv + }; + + // clang-format on +} +TOML_IMPL_NAMESPACE_END; + +#if TOML_ABI_NAMESPACES +#if TOML_EXCEPTIONS +#define TOML_PARSER_TYPENAME TOML_NAMESPACE::impl::impl_ex::parser +#else +#define TOML_PARSER_TYPENAME TOML_NAMESPACE::impl::impl_noex::parser +#endif +#else +#define TOML_PARSER_TYPENAME TOML_NAMESPACE::impl::parser +#endif + +namespace toml +{ +} + +TOML_NAMESPACE_START // abi namespace +{ + inline namespace literals + { + } + + enum class TOML_CLOSED_ENUM node_type : uint8_t + { + none, + table, + array, + string, + integer, + floating_point, + boolean, + date, + time, + date_time + }; + + template + inline std::basic_ostream& operator<<(std::basic_ostream& lhs, node_type rhs) + { + const auto str = impl::node_type_friendly_names[static_cast>(rhs)]; + using str_char_t = decltype(str)::value_type; + if constexpr (std::is_same_v) + return lhs << str; + else + { + if constexpr (sizeof(Char) == sizeof(str_char_t)) + return lhs << std::basic_string_view{ reinterpret_cast(str.data()), str.length() }; + else + return lhs << str.data(); + } + } + + enum class TOML_OPEN_FLAGS_ENUM value_flags : uint16_t // being an "OPEN" flags enum is not an error + { + none, + format_as_binary = 1, + format_as_octal = 2, + format_as_hexadecimal = 3, + }; + TOML_MAKE_FLAGS(value_flags); + + inline constexpr value_flags preserve_source_value_flags = + POXY_IMPLEMENTATION_DETAIL(value_flags{ static_cast>(-1) }); + + enum class TOML_CLOSED_FLAGS_ENUM format_flags : uint64_t + { + none, + quote_dates_and_times = (1ull << 0), + quote_infinities_and_nans = (1ull << 1), + allow_literal_strings = (1ull << 2), + allow_multi_line_strings = (1ull << 3), + allow_real_tabs_in_strings = (1ull << 4), + allow_unicode_strings = (1ull << 5), + allow_binary_integers = (1ull << 6), + allow_octal_integers = (1ull << 7), + allow_hexadecimal_integers = (1ull << 8), + indent_sub_tables = (1ull << 9), + indent_array_elements = (1ull << 10), + indentation = indent_sub_tables | indent_array_elements, + relaxed_float_precision = (1ull << 11), + terse_key_value_pairs = (1ull << 12), + }; + TOML_MAKE_FLAGS(format_flags); + + template + struct TOML_TRIVIAL_ABI inserter + { + static_assert(std::is_reference_v); + + T value; + }; + template + inserter(T&&) -> inserter; + template + inserter(T&) -> inserter; + + using default_formatter = toml_formatter; +} +TOML_NAMESPACE_END; + +TOML_IMPL_NAMESPACE_START +{ + template + using remove_cvref = std::remove_cv_t>; + + template + using common_signed_type = std::common_type_t...>; + + template + inline constexpr bool is_one_of = (false || ... || std::is_same_v); + + template + inline constexpr bool all_integral = (std::is_integral_v && ...); + + template + inline constexpr bool is_cvref = std::is_reference_v || std::is_const_v || std::is_volatile_v; + + template + inline constexpr bool is_wide_string = + is_one_of, const wchar_t*, wchar_t*, std::wstring_view, std::wstring>; + + template + inline constexpr bool value_retrieval_is_nothrow = !std::is_same_v, std::string> +#if TOML_HAS_CHAR8 + && !std::is_same_v, std::u8string> +#endif + + && !is_wide_string; + + template + struct copy_ref_; + template + using copy_ref = typename copy_ref_::type; + + template + struct copy_ref_ + { + using type = Dest; + }; + + template + struct copy_ref_ + { + using type = std::add_lvalue_reference_t; + }; + + template + struct copy_ref_ + { + using type = std::add_rvalue_reference_t; + }; + + template + struct copy_cv_; + template + using copy_cv = typename copy_cv_::type; + + template + struct copy_cv_ + { + using type = Dest; + }; + + template + struct copy_cv_ + { + using type = std::add_const_t; + }; + + template + struct copy_cv_ + { + using type = std::add_volatile_t; + }; + + template + struct copy_cv_ + { + using type = std::add_cv_t; + }; + + template + using copy_cvref = + copy_ref, std::remove_reference_t>, Dest>, Src>; + + template + inline constexpr bool dependent_false = false; + + template + inline constexpr bool first_is_same = false; + template + inline constexpr bool first_is_same = true; + + // general value traits + // (as they relate to their equivalent native TOML type) + template + struct value_traits + { + using native_type = void; + static constexpr bool is_native = false; + static constexpr bool is_losslessly_convertible_to_native = false; + static constexpr bool can_represent_native = false; + static constexpr bool can_partially_represent_native = false; + static constexpr auto type = node_type::none; + }; + + template + struct value_traits : value_traits + {}; + template + struct value_traits : value_traits + {}; + template + struct value_traits : value_traits + {}; + template + struct value_traits : value_traits + {}; + template + struct value_traits : value_traits + {}; + + // integer value_traits specializations - standard types + template + struct integer_limits + { + static constexpr auto min = (std::numeric_limits::min)(); + static constexpr auto max = (std::numeric_limits::max)(); + }; + template + struct integer_traits_base : integer_limits + { + using native_type = int64_t; + static constexpr bool is_native = std::is_same_v; + static constexpr bool is_signed = static_cast(-1) < T{}; // for impls not specializing std::is_signed + static constexpr auto type = node_type::integer; + static constexpr bool can_partially_represent_native = true; + }; + template + struct unsigned_integer_traits : integer_traits_base + { + static constexpr bool is_losslessly_convertible_to_native = integer_limits::max <= 9223372036854775807ULL; + static constexpr bool can_represent_native = false; + }; + template + struct signed_integer_traits : integer_traits_base + { + using native_type = int64_t; + static constexpr bool is_losslessly_convertible_to_native = + integer_limits::min >= (-9223372036854775807LL - 1LL) && integer_limits::max <= 9223372036854775807LL; + static constexpr bool can_represent_native = + integer_limits::min <= (-9223372036854775807LL - 1LL) && integer_limits::max >= 9223372036854775807LL; + }; + template ::is_signed> + struct integer_traits : signed_integer_traits + {}; + template + struct integer_traits : unsigned_integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; + static_assert(value_traits::is_native); + static_assert(value_traits::is_signed); + static_assert(value_traits::is_losslessly_convertible_to_native); + static_assert(value_traits::can_represent_native); + static_assert(value_traits::can_partially_represent_native); + + // integer value_traits specializations - non-standard types +#ifdef TOML_INT128 + template <> + struct integer_limits + { + static constexpr TOML_INT128 max = + static_cast((TOML_UINT128{ 1u } << ((__SIZEOF_INT128__ * CHAR_BIT) - 1)) - 1); + static constexpr TOML_INT128 min = -max - TOML_INT128{ 1 }; + }; + template <> + struct integer_limits + { + static constexpr TOML_UINT128 min = TOML_UINT128{}; + static constexpr TOML_UINT128 max = (2u * static_cast(integer_limits::max)) + 1u; + }; + template <> + struct value_traits : integer_traits + {}; + template <> + struct value_traits : integer_traits + {}; +#endif +#ifdef TOML_SMALL_INT_TYPE + template <> + struct value_traits : signed_integer_traits + {}; +#endif + + // floating-point traits base + template + struct float_traits_base + { + static constexpr auto type = node_type::floating_point; + using native_type = double; + static constexpr bool is_native = std::is_same_v; + static constexpr bool is_signed = true; + + static constexpr int bits = static_cast(sizeof(T) * CHAR_BIT); + static constexpr int digits = MantissaDigits; + static constexpr int digits10 = DecimalDigits; + + static constexpr bool is_losslessly_convertible_to_native = bits <= 64 // + && digits <= 53 // DBL_MANT_DIG + && digits10 <= 15; // DBL_DIG + + static constexpr bool can_represent_native = digits >= 53 // DBL_MANT_DIG + && digits10 >= 15; // DBL_DIG + + static constexpr bool can_partially_represent_native = digits > 0 && digits10 > 0; + }; + template + struct float_traits : float_traits_base::digits, std::numeric_limits::digits10> + {}; +#if TOML_ENABLE_FLOAT16 + template <> + struct float_traits<_Float16> : float_traits_base<_Float16, __FLT16_MANT_DIG__, __FLT16_DIG__> + {}; +#endif +#ifdef TOML_FLOAT128 + template <> + struct float_traits : float_traits_base + {}; +#endif + + // floating-point traits + template <> + struct value_traits : float_traits + {}; + template <> + struct value_traits : float_traits + {}; + template <> + struct value_traits : float_traits + {}; +#if TOML_ENABLE_FLOAT16 + template <> + struct value_traits<_Float16> : float_traits<_Float16> + {}; +#endif +#ifdef TOML_FLOAT128 + template <> + struct value_traits : float_traits + {}; +#endif +#ifdef TOML_SMALL_FLOAT_TYPE + template <> + struct value_traits : float_traits + {}; +#endif + static_assert(value_traits::is_native); + static_assert(value_traits::is_losslessly_convertible_to_native); + static_assert(value_traits::can_represent_native); + static_assert(value_traits::can_partially_represent_native); + + // string value_traits specializations - char-based strings + template + struct string_traits + { + using native_type = std::string; + static constexpr bool is_native = std::is_same_v; + static constexpr bool is_losslessly_convertible_to_native = true; + static constexpr bool can_represent_native = + !std::is_array_v && (!std::is_pointer_v || std::is_const_v>); + static constexpr bool can_partially_represent_native = can_represent_native; + static constexpr auto type = node_type::string; + }; + template <> + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template + struct value_traits : string_traits + {}; + + // string value_traits specializations - char8_t-based strings +#if TOML_HAS_CHAR8 + template <> + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template + struct value_traits : string_traits + {}; + template <> + struct value_traits : string_traits + {}; + template + struct value_traits : string_traits + {}; +#endif + + // string value_traits specializations - wchar_t-based strings on Windows +#if TOML_ENABLE_WINDOWS_COMPAT + template + struct wstring_traits + { + using native_type = std::string; + static constexpr bool is_native = false; + static constexpr bool is_losslessly_convertible_to_native = true; // narrow + static constexpr bool can_represent_native = std::is_same_v; // widen + static constexpr bool can_partially_represent_native = can_represent_native; + static constexpr auto type = node_type::string; + }; + template <> + struct value_traits : wstring_traits + {}; + template <> + struct value_traits : wstring_traits + {}; + template <> + struct value_traits : wstring_traits + {}; + template + struct value_traits : wstring_traits + {}; + template <> + struct value_traits : wstring_traits + {}; + template + struct value_traits : wstring_traits + {}; +#endif + + // other 'native' value_traits specializations + template + struct native_value_traits + { + using native_type = T; + static constexpr bool is_native = true; + static constexpr bool is_losslessly_convertible_to_native = true; + static constexpr bool can_represent_native = true; + static constexpr bool can_partially_represent_native = true; + static constexpr auto type = NodeType; + }; + template <> + struct value_traits : native_value_traits + {}; + template <> + struct value_traits : native_value_traits + {}; + template <> + struct value_traits