diff --git a/Modules/jaspTestModule b/Modules/jaspTestModule index c27e076b5e..50a19622c4 160000 --- a/Modules/jaspTestModule +++ b/Modules/jaspTestModule @@ -1 +1 @@ -Subproject commit c27e076b5eb3e3c3b4699da44564281f64302991 +Subproject commit 50a19622c472b89916c149ad63628c644e77f4a6 diff --git a/QMLComponents/analysisbase.cpp b/QMLComponents/analysisbase.cpp index 30df60bb3b..cded470009 100644 --- a/QMLComponents/analysisbase.cpp +++ b/QMLComponents/analysisbase.cpp @@ -149,7 +149,7 @@ Json::Value& AnalysisBase::_getParentBoundValue(const QVectorisObject() && parentBoundValues->isMember("value") && parentBoundValues->isMember("types")) - parentBoundValues = &(*parentBoundValues)["value"]; + parentBoundValues = &(*parentBoundValues)["value"]; // If the control contain variable names, the option values are inside the 'value' member if (!parentBoundValues->isNull() && parentBoundValues->isArray()) { @@ -157,17 +157,20 @@ Json::Value& AnalysisBase::_getParentBoundValue(const QVectorisObject() && keyValue->isMember("value") && keyValue->isMember("types")) + keyValue = &((*keyValue)["value"]); // The key can be also a variable name + // The value can be a string or an array of strings (for interaction terms) - if (val.isString() && parent.value.size() == 1) + if (keyValue->isString() && parent.value.size() == 1) { - if (val.asString() == parent.value[0]) found = true; + if (keyValue->asString() == parent.value[0]) found = true; } - else if (val.isArray() && val.size() == parent.value.size()) + else if (keyValue->isArray() && keyValue->size() == parent.value.size()) { found = true; size_t i = 0; - for (const Json::Value& compVal : val) + for (const Json::Value& compVal : *keyValue) { if (!compVal.isString() || compVal.asString() != parent.value[i]) found = false; i++; diff --git a/QMLComponents/boundcontrols/boundcontrolbase.cpp b/QMLComponents/boundcontrols/boundcontrolbase.cpp index de485329cd..96a5ee465f 100644 --- a/QMLComponents/boundcontrols/boundcontrolbase.cpp +++ b/QMLComponents/boundcontrols/boundcontrolbase.cpp @@ -150,7 +150,7 @@ void BoundControlBase::_readTableValue(const Json::Value &value, const std::stri } } -Json::Value BoundControlBase::_getTableValueOption(const Terms& terms, const ListModel::RowControlsValues& componentValuesMap, const std::string& key, bool hasMultipleTerms) +Json::Value BoundControlBase::_getTableValueOption(const Terms& terms, const ListModel::RowControlsValues& componentValuesMap, const std::string& key, bool hasInteraction, bool keyHasVariables) { Json::Value result(Json::arrayValue); @@ -158,20 +158,40 @@ Json::Value BoundControlBase::_getTableValueOption(const Terms& terms, const Lis { QMap componentValues = componentValuesMap[term.asQString()]; - Json::Value rowValues(Json::objectValue); - if (hasMultipleTerms) + Json::Value rowValues(Json::objectValue), + keyValue; // depending of keyHasVariables & hasInteraction, keyValue can be an object, an array or a string + + if (keyHasVariables) { - Json::Value keyValue(Json::arrayValue); - for (const std::string& comp : term.scomponents()) - keyValue.append(comp); - rowValues[key] = keyValue; + // If the key of the row contains variables, set 'value' and 'types' + Json::Value jsonValue, jsonTypes; + + if (hasInteraction) + { + for (const std::string& comp : term.scomponents()) + jsonValue.append(comp); + for (columnType type : term.types()) + jsonTypes.append(columnTypeToString(type)); + } + else + { + jsonValue = term.asString(); + jsonTypes = columnTypeToString(term.type()); + } + keyValue["value"] = jsonValue; + keyValue["types"] = jsonTypes; } else { - Json::Value keyValue(term.asString()); - rowValues[key] = keyValue; + if (hasInteraction) + for (const std::string& comp : term.scomponents()) + keyValue.append(comp); + else + keyValue = term.asString(); } + rowValues[key] = keyValue; + QMapIterator it2(componentValues); while (it2.hasNext()) { @@ -184,9 +204,9 @@ Json::Value BoundControlBase::_getTableValueOption(const Terms& terms, const Lis return result; } -void BoundControlBase::_setTableValue(const Terms& terms, const ListModel::RowControlsValues& componentValuesMap, const std::string& key, bool hasMultipleTerms) +void BoundControlBase::_setTableValue(const Terms& terms, const ListModel::RowControlsValues& componentValuesMap, const std::string& key, bool hasInteraction, bool keyHasVariables) { - setBoundValue(_getTableValueOption(terms, componentValuesMap, key, hasMultipleTerms)); + setBoundValue(_getTableValueOption(terms, componentValuesMap, key, hasInteraction, keyHasVariables)); } bool BoundControlBase::_isValueWithTypes(const Json::Value &value) const diff --git a/QMLComponents/boundcontrols/boundcontrolbase.h b/QMLComponents/boundcontrols/boundcontrolbase.h index dd73f021b4..21ff748c41 100644 --- a/QMLComponents/boundcontrols/boundcontrolbase.h +++ b/QMLComponents/boundcontrols/boundcontrolbase.h @@ -48,8 +48,8 @@ class BoundControlBase : public BoundControl std::string getName() const; void handleComputedColumn(const Json::Value& value); - Json::Value _getTableValueOption(const Terms& terms, const ListModel::RowControlsValues& componentValuesMap, const std::string& key, bool hasMultipleTerms); - void _setTableValue(const Terms& terms, const ListModel::RowControlsValues& componentValuesMap, const std::string& key, bool hasMultipleTerms); + Json::Value _getTableValueOption(const Terms& terms, const ListModel::RowControlsValues& componentValuesMap, const std::string& key, bool hasInteraction, bool keyHasVariables); + void _setTableValue(const Terms& terms, const ListModel::RowControlsValues& componentValuesMap, const std::string& key, bool hasInteraction, bool keyHasVariables = false); void _readTableValue(const Json::Value& value, const std::string& key, bool hasMultipleTerms, Terms& terms, ListModel::RowControlsValues& allControlValues); bool _isValueWithTypes(const Json::Value &value) const; diff --git a/QMLComponents/boundcontrols/boundcontrolterms.cpp b/QMLComponents/boundcontrols/boundcontrolterms.cpp index f4fbc213c2..07f8c25ca7 100644 --- a/QMLComponents/boundcontrols/boundcontrolterms.cpp +++ b/QMLComponents/boundcontrols/boundcontrolterms.cpp @@ -140,7 +140,17 @@ void BoundControlTerms::bindTo(const Json::Value &value) for (Term& term : terms) { if (typesPart.size() > i) // If the type is given, use it - term.setType(columnTypeFromString(typesPart[i].asString(), columnType::unknown)); + { + if (typesPart[i].isArray()) + { + columnTypeVec types; + for (const Json::Value& jsonType : typesPart[i]) + types.push_back(columnTypeFromString(jsonType.asString(), columnType::unknown)); + term.setTypes(types); + } + else + term.setType(columnTypeFromString(typesPart[i].asString(), columnType::unknown)); + } else { if (term.type() == columnType::unknown) diff --git a/QMLComponents/controls/componentslistbase.cpp b/QMLComponents/controls/componentslistbase.cpp index c6f53b1a6b..b0fc606300 100644 --- a/QMLComponents/controls/componentslistbase.cpp +++ b/QMLComponents/controls/componentslistbase.cpp @@ -206,7 +206,7 @@ void ComponentsListBase::termsChangedHandler() { JASPListControl::termsChangedHandler(); - _setTableValue(_termsModel->terms(), _termsModel->getTermsWithComponentValues(), fq(_optionKey), containsInteractions()); + _setTableValue(_termsModel->terms(), _termsModel->getTermsWithComponentValues(), fq(_optionKey), containsInteractions(), containsVariables()); bindOffsets(); emit controlNameXOffsetMapChanged(); } @@ -308,7 +308,7 @@ Json::Value ComponentsListBase::getJsonFromComponentValues(const ListModel::RowC for (const QString& term : termsWithComponentValues.keys()) terms.add(Term::readTerm(term)); - return _getTableValueOption(terms, termsWithComponentValues, fq(_optionKey), containsInteractions()); + return _getTableValueOption(terms, termsWithComponentValues, fq(_optionKey), containsInteractions(), containsVariables()); } void ComponentsListBase::addItemHandler() diff --git a/QMLComponents/controls/jasplistcontrol.cpp b/QMLComponents/controls/jasplistcontrol.cpp index 9cc1d44b8f..916c65070d 100644 --- a/QMLComponents/controls/jasplistcontrol.cpp +++ b/QMLComponents/controls/jasplistcontrol.cpp @@ -299,13 +299,6 @@ std::vector JASPListControl::usedVariables() const Json::Value JASPListControl::valueTypes() const { Json::Value types(Json::arrayValue); - strstrmap variableTypeMap; - - // An interaction term has components that can be variables: if the model contains also such variables, the interaction term should get the same types. - // So first check which terms have only 1 component: these terms might be variable names, so keep in a map their types. Use then this map to set the type for interaction terms. - for (const Term& term : model()->terms()) - if (term.components().size() == 1) - variableTypeMap[term.asString()] = columnTypeToString(term.type()); for (const Term& term : model()->terms()) { @@ -314,8 +307,8 @@ Json::Value JASPListControl::valueTypes() const else { Json::Value compTypes(Json::arrayValue); - for (const std::string& component : term.scomponents()) - compTypes.append(variableTypeMap.count(component) > 0 ? variableTypeMap[component] : columnTypeToString(columnType::unknown)); + for (columnType type : term.types()) + compTypes.append(columnTypeToString(type)); types.append(compTypes); } } diff --git a/QMLComponents/models/listmodel.cpp b/QMLComponents/models/listmodel.cpp index a38260e597..3c9e313cc1 100644 --- a/QMLComponents/models/listmodel.cpp +++ b/QMLComponents/models/listmodel.cpp @@ -297,10 +297,9 @@ void ListModel::setVariableType(int ind, columnType type) if (term.type() == type) return; - term.setType(type); - - emit dataChanged(index(ind, 0), index(ind, 0)); - emit columnTypeChanged(term); + Term newTerm(term); + newTerm.setType(type); + sourceColumnTypeChanged(newTerm); } columnType ListModel::getVariableType(const QString& name) const @@ -613,20 +612,37 @@ void ListModel::sourceNamesChanged(QMap map) emit namesChanged(changedNamesMap); } -int ListModel::sourceColumnTypeChanged(Term sourceTerm) +bool ListModel::sourceColumnTypeChanged(Term sourceTerm) { - int i = _terms.indexOf(sourceTerm); - if (i >= 0) + bool change = false; + for (int i = 0; i < _terms.size(); i++) { Term& term = _terms.at(i); - term.setType(sourceTerm.type()); - QModelIndex ind = index(i, 0); + if (term.containsAll(sourceTerm)) + { + // A term may have several components: if all sourceTerm's components are in the term's components, + // then for each component in term that is in sourceTerm, replace its type (in term) by the one in sourceTerm + columnTypeVec types = term.types(), + sourceTypes = sourceTerm.types(); + int sourceInd = 0; - emit dataChanged(ind, ind, {ListModel::ColumnTypeRole, ListModel::ColumnTypeIconRole, ListModel::ColumnTypeDisabledIconRole}); - emit columnTypeChanged(term); - } + for (const QString& sourceComponent : sourceTerm.components()) + { + int ind = term.components().indexOf(sourceComponent); + if (ind >= 0 && types.size() > ind && sourceTypes.size() > sourceInd) + types[ind] = sourceTypes[sourceInd]; + sourceInd++; + } + term.setTypes(types); + QModelIndex ind = index(i, 0); + + emit dataChanged(ind, ind, {ListModel::ColumnTypeRole, ListModel::ColumnTypeIconRole, ListModel::ColumnTypeDisabledIconRole}); + emit columnTypeChanged(term); - return i; + change = true; + } + } + return change; } bool ListModel::sourceLabelsChanged(QString columnName, QMap changedLabels) diff --git a/QMLComponents/models/listmodel.h b/QMLComponents/models/listmodel.h index 940affe7e5..4b8e0227cf 100644 --- a/QMLComponents/models/listmodel.h +++ b/QMLComponents/models/listmodel.h @@ -116,7 +116,7 @@ class ListModel : public QAbstractTableModel, public VariableInfoConsumer public slots: virtual void sourceTermsReset(); virtual void sourceNamesChanged(QMap map); - virtual int sourceColumnTypeChanged(Term sourceTerm); + virtual bool sourceColumnTypeChanged(Term sourceTerm); virtual bool sourceLabelsChanged(QString columnName, QMap changedLabels = {}); virtual bool sourceLabelsReordered(QString columnName); virtual void sourceColumnsChanged(QStringList columns); diff --git a/QMLComponents/models/listmodelavailableinterface.cpp b/QMLComponents/models/listmodelavailableinterface.cpp index b5c205f6a7..10201472b5 100644 --- a/QMLComponents/models/listmodelavailableinterface.cpp +++ b/QMLComponents/models/listmodelavailableinterface.cpp @@ -149,14 +149,14 @@ void ListModelAvailableInterface::sourceColumnsChanged(QStringList columns) emit columnsChanged(changedColumns); } -int ListModelAvailableInterface::sourceColumnTypeChanged(Term term) +bool ListModelAvailableInterface::sourceColumnTypeChanged(Term term) { - int index = ListModelDraggable::sourceColumnTypeChanged(term); + bool change = ListModelDraggable::sourceColumnTypeChanged(term); - if (index == -1 && _allTerms.contains(term)) + if (!change && _allTerms.contains(term)) emit columnTypeChanged(term); - return index; + return change; } bool ListModelAvailableInterface::sourceLabelsChanged(QString columnName, QMap changedLabels) diff --git a/QMLComponents/models/listmodelavailableinterface.h b/QMLComponents/models/listmodelavailableinterface.h index 60fb1c8d88..88da8bef92 100644 --- a/QMLComponents/models/listmodelavailableinterface.h +++ b/QMLComponents/models/listmodelavailableinterface.h @@ -52,7 +52,7 @@ public slots: void sourceTermsReset() override; void sourceNamesChanged(QMap map) override; void sourceColumnsChanged(QStringList columns) override; - int sourceColumnTypeChanged(Term name) override; + bool sourceColumnTypeChanged(Term name) override; bool sourceLabelsChanged(QString columnName, QMap = {}) override; bool sourceLabelsReordered(QString columnName) override; void removeAssignedModel(ListModelAssignedInterface *assignedModel); diff --git a/QMLComponents/models/term.h b/QMLComponents/models/term.h index 90d320e8d4..6248c91555 100644 --- a/QMLComponents/models/term.h +++ b/QMLComponents/models/term.h @@ -47,8 +47,11 @@ class Term bool isDraggable() const { return _draggable; } void setDraggable(bool draggable) { _draggable = draggable; } - columnType type() const { return _type; } - void setType(columnType type) { _type = type; } + // If a term has several components, its type self is unknown, but the components have maybe a type. + columnType type() const { return _types.size() == 1 ? _types[0] : columnType::unknown; } + void setType(columnType type) { _types = {type}; } + columnTypeVec types() const { return _types; } + void setTypes(columnTypeVec types) { _types = types; } typedef QStringList::const_iterator const_iterator; typedef QStringList::iterator iterator; @@ -81,7 +84,7 @@ class Term QStringList _components; QString _asQString; bool _draggable = true; - columnType _type = columnType::unknown; + columnTypeVec _types = {columnType::unknown}; }; #endif // TERM_H diff --git a/QMLComponents/models/terms.cpp b/QMLComponents/models/terms.cpp index 5e5b576c52..dc46d549a9 100644 --- a/QMLComponents/models/terms.cpp +++ b/QMLComponents/models/terms.cpp @@ -152,7 +152,7 @@ void Terms::add(const Term &term, bool isUnique) else if (result == 0) { itr->setDraggable(term.isDraggable()); - itr->setType(term.type()); + itr->setTypes(term.types()); } else if (result < 0) _terms.push_back(term); @@ -165,7 +165,7 @@ void Terms::add(const Term &term, bool isUnique) else { _terms.at(i).setDraggable(term.isDraggable()); - _terms.at(i).setType(term.type()); + _terms.at(i).setTypes(term.types()); } } } @@ -348,19 +348,21 @@ Terms Terms::crossCombinations() const do { vector combination; + columnTypeVec types; for (uint i = 0; i < _terms.size(); i++) { if (!v[i]) { vector components = _terms.at(i).scomponents(); + columnTypeVec termTypes = _terms.at(i).types(); combination.insert(combination.end(), components.begin(), components.end()); + types.insert(types.end(), termTypes.begin(), termTypes.end()); } } - if (combination.size() == 1) - t.add(at(indexOf(combination[0]))); - else - t.add(Term(combination)); + Term newTerm(combination); + newTerm.setTypes(types); + t.add(newTerm); } while (std::next_permutation(v.begin(), v.end())); } @@ -380,19 +382,21 @@ Terms Terms::wayCombinations(int ways) const do { vector combination; + columnTypeVec types; for (uint i = 0; i < _terms.size(); ++i) { if (!v[i]) { vector components = _terms.at(i).scomponents(); + columnTypeVec termTypes = _terms.at(i).types(); combination.insert(combination.end(), components.begin(), components.end()); + types.insert(types.end(), termTypes.begin(), termTypes.end()); } } - if (combination.size() == 1) - t.add(at(indexOf(combination[0]))); - else - t.add(Term(combination)); + Term newTerm(combination); + newTerm.setTypes(types); + t.add(newTerm); } while (std::next_permutation(v.begin(), v.end())); } @@ -415,11 +419,15 @@ Terms Terms::ffCombinations(const Terms &terms) { for (uint j = 0; j < combos.size(); j++) { - QStringList term = _terms.at(i).components(); - QStringList newTerm = combos.at(j).components(); - - term.append(newTerm); - newTerms.add(Term(term)); + QStringList termComponents = _terms.at(i).components(); + termComponents.append(combos.at(j).components()); + columnTypeVec types = _terms.at(i).types(); + columnTypeVec coTypes = combos.at(j).types(); + types.insert(types.end(), coTypes.begin(), coTypes.end()); + + Term newTerm(termComponents); + newTerm.setTypes(types); + newTerms.add(newTerm); } }