diff --git a/guh-control/devicemanager.cpp b/guh-control/devicemanager.cpp index b70dd780..fc9f5447 100644 --- a/guh-control/devicemanager.cpp +++ b/guh-control/devicemanager.cpp @@ -133,7 +133,6 @@ void DeviceManager::getSupportedDevicesResponse(const QVariantMap ¶ms) QVariantList deviceClassList = params.value("params").toMap().value("deviceClasses").toList(); foreach (QVariant deviceClassVariant, deviceClassList) { DeviceClass *deviceClass = JsonTypes::unpackDeviceClass(deviceClassVariant.toMap(), Engine::instance()->deviceManager()->deviceClasses()); - qDebug() << "Server has device class:" << deviceClass->name() << deviceClass->id(); m_deviceClasses->addDeviceClass(deviceClass); } } @@ -280,7 +279,7 @@ void DeviceManager::addDiscoveredDevice(const QUuid &deviceClassId, const QUuid params.insert("deviceClassId", deviceClassId.toString()); params.insert("name", name); params.insert("deviceDescriptorId", deviceDescriptorId.toString()); - m_jsonClient->sendCommand("Devices.AddConfiguredDevice", params); + m_jsonClient->sendCommand("Devices.AddConfiguredDevice", params, this, "addDeviceResponse"); } void DeviceManager::pairDevice(const QUuid &deviceClassId, const QUuid &deviceDescriptorId, const QString &name) diff --git a/guh-control/discovery/upnpdiscovery.cpp b/guh-control/discovery/upnpdiscovery.cpp index b7316963..16252913 100644 --- a/guh-control/discovery/upnpdiscovery.cpp +++ b/guh-control/discovery/upnpdiscovery.cpp @@ -148,7 +148,7 @@ void UpnpDiscovery::readData() if (key.contains("Server") || key.contains("SERVER")) { - if (value.contains("guh")) { + if (value.contains("nymea")) { qDebug() << " --> " << key << value; isGuh = true; } @@ -160,6 +160,10 @@ void UpnpDiscovery::readData() } } + if (isGuh) { + qDebug() << "Found guh device:" << location; + } + if (!m_foundDevices.contains(location) && isGuh) { m_foundDevices.append(location); DiscoveryDevice discoveryDevice; @@ -201,7 +205,7 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply) } if (xml.isStartElement()) { - if (xml.name().toString() == "guhRpcURL") { + if (xml.name().toString() == "nymeaRpcURL") { discoveryDevice.setGuhRpcUrl(xml.readElementText()); } } diff --git a/guh-control/discovery/zeroconfdiscovery.cpp b/guh-control/discovery/zeroconfdiscovery.cpp index 8be89e00..d1a4400e 100644 --- a/guh-control/discovery/zeroconfdiscovery.cpp +++ b/guh-control/discovery/zeroconfdiscovery.cpp @@ -31,7 +31,7 @@ bool ZeroconfDiscovery::discovering() const #ifdef WITH_AVAHI void ZeroconfDiscovery::serviceEntryAdded(const AvahiServiceEntry &entry) { - if (!entry.name().startsWith("guhIO") || entry.serviceType() != "_jsonrpc._tcp") { + if (!entry.name().startsWith("nymea") || entry.serviceType() != "_jsonrpc._tcp") { return; } qDebug() << "avahi service entry added" << entry.name() << entry.hostAddress() << entry.port() << entry.txt() << entry.serviceType(); @@ -57,7 +57,7 @@ void ZeroconfDiscovery::serviceEntryAdded(const AvahiServiceEntry &entry) dev.setHostAddress(entry.hostAddress()); dev.setPort(entry.port()); dev.setFriendlyName(entry.hostName()); - dev.setGuhRpcUrl(QString("%1://%2:%3").arg(sslEnabled ? "guhs" : "guh").arg(entry.hostAddress().toString()).arg(entry.port())); + dev.setGuhRpcUrl(QString("%1://%2:%3").arg(sslEnabled ? "nymeas" : "nymea").arg(entry.hostAddress().toString()).arg(entry.port())); m_discoveryModel->addDevice(dev); // DiscoveryDevice *dev = new DiscoveryDevice(); diff --git a/guh-control/guhconnection.cpp b/guh-control/guhconnection.cpp index c162ed22..8d7b85de 100644 --- a/guh-control/guhconnection.cpp +++ b/guh-control/guhconnection.cpp @@ -65,7 +65,7 @@ QString GuhConnection::url() const void GuhConnection::sendData(const QByteArray &data) { if (connected()) { - qDebug() << "sending data:" << data; +// qDebug() << "sending data:" << data; m_currentInterface->sendData(data); } else { qWarning() << "Not connected. Cannot send."; diff --git a/guh-control/jsonrpc/jsontypes.cpp b/guh-control/jsonrpc/jsontypes.cpp index 6c130419..cd6549bd 100644 --- a/guh-control/jsonrpc/jsontypes.cpp +++ b/guh-control/jsonrpc/jsontypes.cpp @@ -31,6 +31,9 @@ #include "types/eventdescriptors.h" #include "types/ruleactionparam.h" #include "types/ruleactionparams.h" +#include "types/stateevaluator.h" +#include "types/stateevaluators.h" +#include "types/statedescriptor.h" #include @@ -223,56 +226,74 @@ QVariantMap JsonTypes::packRule(Rule *rule) ret.insert("name", rule->name()); ret.insert("enabled", rule->enabled()); - if (rule->ruleActions()->rowCount() > 0) { - QVariantList actions; - for (int i = 0; i < rule->ruleActions()->rowCount(); i++) { - QVariantMap ruleAction; - ruleAction.insert("actionTypeId", rule->ruleActions()->get(i)->actionTypeId()); - ruleAction.insert("deviceId", rule->ruleActions()->get(i)->deviceId()); - if (rule->ruleActions()->get(i)->ruleActionParams()->rowCount() > 0) { - QVariantList ruleActionParams; - for (int j = 0; j < rule->ruleActions()->get(i)->ruleActionParams()->rowCount(); j++) { - QVariantMap ruleActionParam; - ruleActionParam.insert("paramTypeId", rule->ruleActions()->get(i)->ruleActionParams()->get(j)->paramTypeId()); - ruleActionParam.insert("value", rule->ruleActions()->get(i)->ruleActionParams()->get(j)->value()); - ruleActionParams.append(ruleActionParam); - } - ruleAction.insert("ruleActionParams", ruleActionParams); - } - actions.append(ruleAction); - } - ret.insert("actions", actions); + if (rule->actions()->rowCount() > 0) { + ret.insert("actions", packRuleActions(rule->actions())); + } + if (rule->exitActions()->rowCount() > 0) { + ret.insert("exitActions", packRuleActions(rule->exitActions())); } if (rule->eventDescriptors()->rowCount() > 0) { - QVariantList eventDescriptors; - for (int i = 0; i < rule->eventDescriptors()->rowCount(); i++) { - QVariantMap eventDescriptorMap; - EventDescriptor* eventDescriptor = rule->eventDescriptors()->get(i); - if (!eventDescriptor->deviceId().isNull() && !eventDescriptor->eventTypeId().isNull()) { - eventDescriptorMap.insert("eventTypeId", eventDescriptor->eventTypeId()); - eventDescriptorMap.insert("deviceId", eventDescriptor->deviceId()); - } else { - eventDescriptorMap.insert("interface", eventDescriptor->interfaceName()); - eventDescriptorMap.insert("interfaceEvent", eventDescriptor->interfaceEvent()); - } - if (eventDescriptor->paramDescriptors()->rowCount() > 0) { - QVariantList paramDescriptors; - for (int j = 0; j < rule->eventDescriptors()->get(i)->paramDescriptors()->rowCount(); j++) { - QVariantMap paramDescriptor; - paramDescriptor.insert("paramTypeId", rule->eventDescriptors()->get(i)->paramDescriptors()->get(j)->paramTypeId()); - paramDescriptor.insert("value", rule->eventDescriptors()->get(i)->paramDescriptors()->get(j)->value()); - QMetaEnum operatorEnum = QMetaEnum::fromType(); - paramDescriptor.insert("operator", operatorEnum.valueToKey(rule->eventDescriptors()->get(i)->paramDescriptors()->get(j)->operatorType())); - paramDescriptors.append(paramDescriptor); - } - eventDescriptorMap.insert("paramDescriptors", paramDescriptors); - } - eventDescriptors.append(eventDescriptorMap); - } - ret.insert("eventDescriptors", eventDescriptors); + ret.insert("eventDescriptors", packEventDescriptors(rule->eventDescriptors())); } + if (rule->stateEvaluator()) { + ret.insert("stateEvaluator", packStateEvaluator(rule->stateEvaluator())); + } + + return ret; +} + +QVariantList JsonTypes::packRuleActions(RuleActions *ruleActions) +{ + QVariantList ret; + for (int i = 0; i < ruleActions->rowCount(); i++) { + QVariantMap ruleAction; + ruleAction.insert("actionTypeId", ruleActions->get(i)->actionTypeId()); + ruleAction.insert("deviceId", ruleActions->get(i)->deviceId()); + if (ruleActions->get(i)->ruleActionParams()->rowCount() > 0) { + QVariantList ruleActionParams; + for (int j = 0; j < ruleActions->get(i)->ruleActionParams()->rowCount(); j++) { + QVariantMap ruleActionParam; + ruleActionParam.insert("paramTypeId", ruleActions->get(i)->ruleActionParams()->get(j)->paramTypeId()); + ruleActionParam.insert("value", ruleActions->get(i)->ruleActionParams()->get(j)->value()); + ruleActionParams.append(ruleActionParam); + } + ruleAction.insert("ruleActionParams", ruleActionParams); + } + ret.append(ruleAction); + } + + return ret; +} + +QVariantList JsonTypes::packEventDescriptors(EventDescriptors *eventDescriptors) +{ + QVariantList ret; + for (int i = 0; i < eventDescriptors->rowCount(); i++) { + QVariantMap eventDescriptorMap; + EventDescriptor* eventDescriptor = eventDescriptors->get(i); + if (!eventDescriptor->deviceId().isNull() && !eventDescriptor->eventTypeId().isNull()) { + eventDescriptorMap.insert("eventTypeId", eventDescriptor->eventTypeId()); + eventDescriptorMap.insert("deviceId", eventDescriptor->deviceId()); + } else { + eventDescriptorMap.insert("interface", eventDescriptor->interfaceName()); + eventDescriptorMap.insert("interfaceEvent", eventDescriptor->interfaceEvent()); + } + if (eventDescriptor->paramDescriptors()->rowCount() > 0) { + QVariantList paramDescriptors; + for (int j = 0; j < eventDescriptor->paramDescriptors()->rowCount(); j++) { + QVariantMap paramDescriptor; + paramDescriptor.insert("paramTypeId", eventDescriptor->paramDescriptors()->get(j)->paramTypeId()); + paramDescriptor.insert("value", eventDescriptor->paramDescriptors()->get(j)->value()); + QMetaEnum operatorEnum = QMetaEnum::fromType(); + paramDescriptor.insert("operator", operatorEnum.valueToKey(eventDescriptor->paramDescriptors()->get(j)->operatorType())); + paramDescriptors.append(paramDescriptor); + } + eventDescriptorMap.insert("paramDescriptors", paramDescriptors); + } + ret.append(eventDescriptorMap); + } return ret; } @@ -284,6 +305,26 @@ QVariantMap JsonTypes::packParam(Param *param) return ret; } +QVariantMap JsonTypes::packStateEvaluator(StateEvaluator *stateEvaluator) +{ + QVariantMap ret; + QMetaEnum stateOperatorEnum = QMetaEnum::fromType(); + ret.insert("operator", stateOperatorEnum.valueToKey(stateEvaluator->stateOperator())); + QVariantMap stateDescriptor; + stateDescriptor.insert("deviceId", stateEvaluator->stateDescriptor()->deviceId()); + QMetaEnum valueOperatorEnum = QMetaEnum::fromType(); + stateDescriptor.insert("operator", valueOperatorEnum.valueToKeys(stateEvaluator->stateDescriptor()->valueOperator())); + stateDescriptor.insert("stateTypeId", stateEvaluator->stateDescriptor()->stateTypeId()); + stateDescriptor.insert("value", stateEvaluator->stateDescriptor()->value()); + ret.insert("stateDescriptor", stateDescriptor); + QVariantList childEvaluators; + for (int i = 0; i < stateEvaluator->childEvaluators()->rowCount(); i++) { + childEvaluators.append(packStateEvaluator(stateEvaluator->childEvaluators()->get(i))); + } + ret.insert("childEvaluators", childEvaluators); + return ret; +} + DeviceClass::SetupMethod JsonTypes::stringToSetupMethod(const QString &setupMethodString) { if (setupMethodString == "SetupMethodJustAdd") { diff --git a/guh-control/jsonrpc/jsontypes.h b/guh-control/jsonrpc/jsontypes.h index 6c447079..4a5c7d33 100644 --- a/guh-control/jsonrpc/jsontypes.h +++ b/guh-control/jsonrpc/jsontypes.h @@ -27,17 +27,22 @@ #include #include "types/types.h" -#include "types/device.h" -#include "types/plugin.h" #include "types/deviceclass.h" -#include "types/paramtype.h" -#include "types/statetype.h" -#include "types/state.h" -#include "types/eventtype.h" -#include "types/actiontype.h" +class Plugin; class Vendor; + +class StateType; +class EventType; +class ActionType; +class ParamType; + +class Device; +class Param; class Rule; +class StateEvaluator; +class RuleActions; +class EventDescriptors; class JsonTypes : public QObject { @@ -56,7 +61,10 @@ public: static Device *unpackDevice(const QVariantMap &deviceMap, QObject *parent); static QVariantMap packRule(Rule* rule); + static QVariantList packRuleActions(RuleActions* ruleActions); + static QVariantList packEventDescriptors(EventDescriptors* eventDescriptors); static QVariantMap packParam(Param *param); + static QVariantMap packStateEvaluator(StateEvaluator* stateEvaluator); private: static DeviceClass::SetupMethod stringToSetupMethod(const QString &setupMethodString); static QList stringListToBasicTags(const QStringList &basicTagsStringList); diff --git a/guh-control/main.cpp b/guh-control/main.cpp index 3ccf7bc9..6632de61 100644 --- a/guh-control/main.cpp +++ b/guh-control/main.cpp @@ -44,6 +44,9 @@ #include "types/rule.h" #include "types/interfaces.h" #include "types/interface.h" +#include "types/statedescriptor.h" +#include "types/stateevaluator.h" +#include "types/stateevaluators.h" #include "models/logsmodel.h" #include "models/valuelogsproxymodel.h" #include "basicconfiguration.h" @@ -131,6 +134,9 @@ int main(int argc, char *argv[]) qmlRegisterType(uri, 1, 0, "Param"); qmlRegisterUncreatableType(uri, 1, 0, "ParamDescriptor", "Uncreatable"); qmlRegisterUncreatableType(uri, 1, 0, "ParamDescriptors", "Uncreatable"); + qmlRegisterUncreatableType(uri, 1, 0, "StateDescriptor", "Uncreatable"); + qmlRegisterUncreatableType(uri, 1, 0, "StateEvaluator", "Uncreatable"); + qmlRegisterUncreatableType(uri, 1, 0, "StateEvaluators", "Uncreatable"); qmlRegisterUncreatableType(uri, 1, 0, "Interface", "Uncreatable"); qmlRegisterSingletonType(uri, 1, 0, "Interfaces", interfacesModel_provider); diff --git a/guh-control/models/logsmodel.cpp b/guh-control/models/logsmodel.cpp index 5fbe49de..0106ca18 100644 --- a/guh-control/models/logsmodel.cpp +++ b/guh-control/models/logsmodel.cpp @@ -155,7 +155,7 @@ void LogsModel::update() void LogsModel::logsReply(const QVariantMap &data) { - qDebug() << "logs reply"; + qDebug() << "logs reply" << data; m_busy = false; emit busyChanged(); beginResetModel(); @@ -167,12 +167,12 @@ void LogsModel::logsReply(const QVariantMap &data) QVariantMap entryMap = logEntryVariant.toMap(); QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong()); QString deviceId = entryMap.value("deviceId").toString(); - QVariant value = entryMap.value("value"); QString typeId = entryMap.value("typeId").toString(); QMetaEnum sourceEnum = QMetaEnum::fromType(); LogEntry::LoggingSource loggingSource = (LogEntry::LoggingSource)sourceEnum.keyToValue(entryMap.value("source").toByteArray()); QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType(); LogEntry::LoggingEventType loggingEventType = (LogEntry::LoggingEventType)loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray()); + QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value"); LogEntry *entry = new LogEntry(timeStamp, value, deviceId, typeId, loggingSource, loggingEventType, this); m_list.append(entry); } @@ -191,12 +191,12 @@ void LogsModel::newLogEntryReceived(const QVariantMap &data) QVariantMap entryMap = data; QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong()); QString deviceId = entryMap.value("deviceId").toString(); - QVariant value = entryMap.value("value"); QString typeId = entryMap.value("typeId").toString(); QMetaEnum sourceEnum = QMetaEnum::fromType(); LogEntry::LoggingSource loggingSource = (LogEntry::LoggingSource)sourceEnum.keyToValue(entryMap.value("source").toByteArray()); QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType(); LogEntry::LoggingEventType loggingEventType = (LogEntry::LoggingEventType)loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray()); + QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value"); LogEntry *entry = new LogEntry(timeStamp, value, deviceId, typeId, loggingSource, loggingEventType, this); m_list.append(entry); endInsertRows(); diff --git a/guh-control/models/rulesfiltermodel.cpp b/guh-control/models/rulesfiltermodel.cpp index d9bbb603..ce0e8c45 100644 --- a/guh-control/models/rulesfiltermodel.cpp +++ b/guh-control/models/rulesfiltermodel.cpp @@ -66,8 +66,17 @@ bool RulesFilterModel::filterAcceptsRow(int source_row, const QModelIndex &sourc found = true; } if (!found) { - for (int i = 0; i < rule->ruleActions()->rowCount(); i++) { - RuleAction *ra = rule->ruleActions()->get(i); + for (int i = 0; i < rule->actions()->rowCount(); i++) { + RuleAction *ra = rule->actions()->get(i); + if (ra->deviceId() == m_filterDeviceId) { + found = true; + break; + } + } + } + if (!found) { + for (int i = 0; i < rule->exitActions()->rowCount(); i++) { + RuleAction *ra = rule->exitActions()->get(i); if (ra->deviceId() == m_filterDeviceId) { found = true; break; diff --git a/guh-control/resources.qrc b/guh-control/resources.qrc index 3267a72d..ea513aa6 100644 --- a/guh-control/resources.qrc +++ b/guh-control/resources.qrc @@ -134,5 +134,11 @@ ui/fonts/Ubuntu-R.ttf ui/fonts/Ubuntu-RI.ttf ui/components/InterfacesModels.qml + ui/magic/StateEvaluatorDelegate.qml + ui/magic/EditStateEvaluatorPage.qml + ui/actiondelegates-ng/ActionDelegate.qml + ui/magic/SimpleStateEvaluatorDelegate.qml + ui/magic/SelectStateDescriptorParamsPage.qml + ui/magic/SelectStateDescriptorPage.qml diff --git a/guh-control/rulemanager.cpp b/guh-control/rulemanager.cpp index e15841ca..c7640eaf 100644 --- a/guh-control/rulemanager.cpp +++ b/guh-control/rulemanager.cpp @@ -10,6 +10,7 @@ #include "types/ruleactionparams.h" #include "types/ruleactionparam.h" #include "types/stateevaluator.h" +#include "types/stateevaluators.h" #include "types/statedescriptor.h" #include @@ -68,6 +69,7 @@ void RuleManager::removeRule(const QUuid &ruleId) void RuleManager::editRule(Rule *rule) { QVariantMap params = JsonTypes::packRule(rule); + qWarning() << "Packed rule:" << params; m_jsonClient->sendCommand("Rules.EditRule", params, this, "onEditRuleReply"); } @@ -97,7 +99,8 @@ void RuleManager::handleRulesNotification(const QVariantMap ¶ms) } m_rules->remove(ruleId); m_rules->insert(parseRule(ruleMap)); - + } else if (params.value("notification").toString() == "Rules.RuleActiveChanged") { + m_rules->getRule(params.value("params").toMap().value("ruleId").toUuid())->setActive(params.value("params").toMap().value("active").toBool()); } else { qWarning() << "Unhandled rule notification" << params; } @@ -113,10 +116,12 @@ void RuleManager::getRulesReply(const QVariantMap ¶ms) QUuid ruleId = ruleDescriptionVariant.toMap().value("id").toUuid(); QString name = ruleDescriptionVariant.toMap().value("name").toString(); bool enabled = ruleDescriptionVariant.toMap().value("enabled").toBool(); + bool active = ruleDescriptionVariant.toMap().value("active").toBool(); Rule *rule = new Rule(ruleId, m_rules); rule->setName(name); rule->setEnabled(enabled); + rule->setActive(active); m_rules->insert(rule); QVariantMap requestParams; @@ -136,7 +141,8 @@ void RuleManager::getRuleDetailsReply(const QVariantMap ¶ms) qDebug() << "got rule details for rule" << ruleMap; parseEventDescriptors(ruleMap.value("eventDescriptors").toList(), rule); parseRuleActions(ruleMap.value("actions").toList(), rule); - parseStateEvaluator(ruleMap.value("stateEvaluator").toMap()); + parseRuleExitActions(ruleMap.value("exitActions").toList(), rule); + rule->setStateEvaluator(parseStateEvaluator(ruleMap.value("stateEvaluator").toMap())); } void RuleManager::onAddRuleReply(const QVariantMap ¶ms) @@ -167,9 +173,9 @@ Rule *RuleManager::parseRule(const QVariantMap &ruleMap) rule->setEnabled(enabled); rule->setActive(active); parseEventDescriptors(ruleMap.value("eventDescriptors").toList(), rule); - StateEvaluator* stateEvaluator = parseStateEvaluator(ruleMap.value("stateEvaluator").toMap()); - stateEvaluator->setParent(rule); parseRuleActions(ruleMap.value("actions").toList(), rule); + parseRuleExitActions(ruleMap.value("exitActions").toList(), rule); + rule->setStateEvaluator(parseStateEvaluator(ruleMap.value("stateEvaluator").toMap())); return rule; } @@ -193,28 +199,21 @@ void RuleManager::parseEventDescriptors(const QVariantList &eventDescriptorList, StateEvaluator *RuleManager::parseStateEvaluator(const QVariantMap &stateEvaluatorMap) { + qDebug() << "bla" << stateEvaluatorMap; StateEvaluator *stateEvaluator = new StateEvaluator(this); if (stateEvaluatorMap.contains("stateDescriptor")) { - QVariantMap sdMap = stateEvaluatorMap.value("sateDescriptor").toMap(); - QString operatorString = sdMap.value("stateOperator").toString(); - StateDescriptor::ValueOperator op; - if (operatorString == "ValueOperatorEquals") { - op = StateDescriptor::ValueOperatorEquals; - } else if (operatorString == "ValueOperatorNotEquals") { - op = StateDescriptor::ValueOperatorNotEquals; - } else if (operatorString == "ValueOperatorLess") { - op = StateDescriptor::ValueOperatorLess; - } else if (operatorString == "ValueOperatorGreater") { - op = StateDescriptor::ValueOperatorGreater; - } else if (operatorString == "ValueOperatorLessOrEqual") { - op = StateDescriptor::ValueOperatorLessOrEqual; - } else if (operatorString == "ValueOperatorGreaterOrEqual") { - op = StateDescriptor::ValueOperatorGreaterOrEqual; - } + QVariantMap sdMap = stateEvaluatorMap.value("stateDescriptor").toMap(); + QMetaEnum operatorEnum = QMetaEnum::fromType(); + StateDescriptor::ValueOperator op = (StateDescriptor::ValueOperator)operatorEnum.keyToValue(sdMap.value("operator").toByteArray()); StateDescriptor *sd = new StateDescriptor(sdMap.value("deviceId").toUuid(), op, sdMap.value("stateTypeId").toUuid(), sdMap.value("value"), stateEvaluator); stateEvaluator->setStateDescriptor(sd); - } + + foreach (const QVariant &childEvaluatorVariant, stateEvaluatorMap.value("childEvaluators").toList()) { + stateEvaluator->childEvaluators()->addStateEvaluator(parseStateEvaluator(childEvaluatorVariant.toMap())); + } + QMetaEnum operatorEnum = QMetaEnum::fromType(); + stateEvaluator->setStateOperator((StateEvaluator::StateOperator)operatorEnum.keyToValue(stateEvaluatorMap.value("operator").toByteArray())); return stateEvaluator; } @@ -230,6 +229,22 @@ void RuleManager::parseRuleActions(const QVariantList &ruleActions, Rule *rule) param->setValue(ruleActionParamVariant.toMap().value("value")); ruleAction->ruleActionParams()->addRuleActionParam(param); } - rule->ruleActions()->addRuleAction(ruleAction); + rule->actions()->addRuleAction(ruleAction); + } +} + +void RuleManager::parseRuleExitActions(const QVariantList &ruleActions, Rule *rule) +{ + foreach (const QVariant &ruleActionVariant, ruleActions) { + RuleAction *ruleAction = new RuleAction(); + ruleAction->setDeviceId(ruleActionVariant.toMap().value("deviceId").toUuid()); + ruleAction->setActionTypeId(ruleActionVariant.toMap().value("actionTypeId").toUuid()); + foreach (const QVariant &ruleActionParamVariant, ruleActionVariant.toMap().value("ruleActionParams").toList()) { + RuleActionParam *param = new RuleActionParam(); + param->setParamTypeId(ruleActionParamVariant.toMap().value("paramTypeId").toUuid()); + param->setValue(ruleActionParamVariant.toMap().value("value")); + ruleAction->ruleActionParams()->addRuleActionParam(param); + } + rule->exitActions()->addRuleAction(ruleAction); } } diff --git a/guh-control/rulemanager.h b/guh-control/rulemanager.h index c4a61eb4..9eb85dc0 100644 --- a/guh-control/rulemanager.h +++ b/guh-control/rulemanager.h @@ -44,6 +44,7 @@ private: void parseEventDescriptors(const QVariantList &eventDescriptorList, Rule *rule); StateEvaluator* parseStateEvaluator(const QVariantMap &stateEvaluatorMap); void parseRuleActions(const QVariantList &ruleActions, Rule *rule); + void parseRuleExitActions(const QVariantList &ruleActions, Rule *rule); signals: void addRuleReply(const QString &ruleError); diff --git a/guh-control/tcpsocketinterface.cpp b/guh-control/tcpsocketinterface.cpp index 3aae1766..428f90b4 100644 --- a/guh-control/tcpsocketinterface.cpp +++ b/guh-control/tcpsocketinterface.cpp @@ -16,7 +16,7 @@ TcpSocketInterface::TcpSocketInterface(QObject *parent) : GuhInterface(parent) QStringList TcpSocketInterface::supportedSchemes() const { - return {"guh", "guhs"}; + return {"nymea", "nymeas"}; } void TcpSocketInterface::sendData(const QByteArray &data) @@ -31,7 +31,7 @@ void TcpSocketInterface::ignoreSslErrors(const QList &errors) void TcpSocketInterface::onConnected() { - if (m_url.scheme() == "guh") { + if (m_url.scheme() == "nymea") { qDebug() << "TCP socket connected"; emit connected(); } @@ -46,10 +46,10 @@ void TcpSocketInterface::onEncrypted() void TcpSocketInterface::connect(const QUrl &url) { m_url = url; - if (url.scheme() == "guhs") { + if (url.scheme() == "nymeas") { qDebug() << "connecting to" << url.host() << url.port(); m_socket.connectToHostEncrypted(url.host(), url.port()); - } else if (url.scheme() == "guh") { + } else if (url.scheme() == "nymea") { m_socket.connectToHost(url.host(), url.port()); } else { qWarning() << "Unsupported scheme"; diff --git a/guh-control/ui/ConnectPage.qml b/guh-control/ui/ConnectPage.qml index eb4446a0..cd40b05c 100644 --- a/guh-control/ui/ConnectPage.qml +++ b/guh-control/ui/ConnectPage.qml @@ -130,11 +130,26 @@ Page { Component { id: connectingPage Page { - Label { + ColumnLayout { anchors.centerIn: parent width: parent.width - app.margins * 2 - text: "Connecting to your guh box..." - font.pixelSize: app.largeFont + spacing: app.margins + + Label { + text: qsTr("Connecting to your guh box...") + wrapMode: Text.WordWrap + font.pixelSize: app.largeFont + Layout.fillWidth: true + } + + Button { + text: "Cancel" + Layout.fillWidth: true + onClicked: { + Engine.connection.disconnect() + pageStack.pop(); + } + } } } } diff --git a/guh-control/ui/actiondelegates-ng/ActionDelegate.qml b/guh-control/ui/actiondelegates-ng/ActionDelegate.qml new file mode 100644 index 00000000..256a4f97 --- /dev/null +++ b/guh-control/ui/actiondelegates-ng/ActionDelegate.qml @@ -0,0 +1,287 @@ +import QtQuick 2.8 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import Guh 1.0 +import "../components" + +ItemDelegate { + id: root + + property var actionType: null + property var actionState: null + + signal executeAction(var params) + + contentItem: ColumnLayout { + RowLayout { + Label { + Layout.fillWidth: true + text: root.actionType.displayName + elide: Text.ElideRight + } + Loader { + id: loader + Layout.fillWidth: sourceComponent == textFieldComponent + sourceComponent: { + if (root.actionType.paramTypes.count !== 1) { + return buttonComponent + } + + var paramType = root.actionType.paramTypes.get(0); + switch (paramType.type.toLowerCase()) { + case "bool": + return boolComponent; + case "int": + return stringComponent; + case "string": + case "qstring": + if (paramType.allowedValues.length > 0) { + return comboBoxComponent; + } + return textFieldComponent; + case "color": + return colorPreviewComponent; + } + console.warn("Param Delegate: Fallback to stringComponent", paramType.name, paramType.type) + return stringComponent; + } + } + Binding { + target: loader.item + when: loader.item + property: "paramType" + value: root.actionType.paramTypes.get(0) + } + Binding { + target: loader.item + when: loader.item + property: "value" + value: root.actionState + } + } + Repeater { + id: paramRepeater + model: root.actionType.paramTypes + delegate: Loader { + id: bottomLoader + property var paramType: root.actionType.paramTypes.get(index) + Layout.fillWidth: true + sourceComponent: { + switch (paramType.type.toLowerCase()) { + case "int": + case "double": + if (paramType.minValue != undefined && paramType.maxValue != undefined) { + return sliderComponent + } + break; + case "color": + return colorPickerComponent + case "string": + return paramType.allowedValues.length === 0 ? textFieldComponent : null + } + return null; + } + + Binding { + target: bottomLoader.item + when: bottomLoader.item + property: "paramType" + value: bottomLoader.paramType + } + Binding { + target: bottomLoader.item + when: bottomLoader.item && root.actionState + property: "value" + value: root.actionState + } + } + } + } + + Component { + id: stringComponent + Label { + property var paramType: null + property var value: null + text: { + switch (paramType.type.toLowerCase()) { + case "int": + return Math.round(value); + } + return value; + } + } + } + Component { + id: boolComponent + Switch { + checked: root.actionState === true + onClicked: { + var params = []; + var param1 = new Object(); + param1["paramTypeId"] = root.actionType.paramTypes.get(0).id; + param1["value"] = checked; + params.push(param1) + root.executeAction(params) + } + } + } + Component { + id: sliderComponent + RowLayout { + id: sliderRow + spacing: app.margins + property var paramType: null + property var value: null + Label { + text: sliderRow.paramType.minValue + } + Slider { + Layout.fillWidth: true + from: sliderRow.paramType.minValue + to: sliderRow.paramType.maxValue + value: sliderRow.value + stepSize: { + switch (sliderRow.paramType.type) { + case "Int": + return 1; + } + return 0.01; + + } + + onValueChanged: { + if (pressed) { + var params = []; + var param1 = new Object(); + param1["paramTypeId"] = sliderRow.paramType.id; + param1["value"] = value; + params.push(param1) + root.executeAction(params) + } + } + } + Label { + text: sliderRow.paramType.maxValue + } + } + + } + + Component { + id: textFieldComponent + RowLayout { + property alias value: textField.text + property var paramType: null + spacing: app.margins + Label { + text: paramType.displayName + } + + TextField { + id: textField + Layout.fillWidth: true + } + } + } + + Component { + id: comboBoxComponent + ComboBox { + id: box + model: paramType.allowedValues + currentIndex: paramType.allowedValues.indexOf(value) + property var paramType: null + property var value: null + onActivated: { + value = paramType.allowedValues[index] + var params = []; + var param1 = new Object(); + param1["paramTypeId"] = paramType.id; + param1["value"] = currentText; + params.push(param1) + root.executeAction(params) + } + } + } + + Component { + id: colorPickerComponent + ColorPicker { + id: colorPicker + implicitHeight: 200 +// color: root.param.value + + Binding { + target: colorPicker + property: "color" + value: root.actionState + when: !colorPicker.pressed + } + + property var lastSentTime: new Date() + onColorChanged: { + var currentTime = new Date(); + if (pressed && currentTime - lastSentTime > 200) { + var params = []; + var param1 = new Object(); + param1["paramTypeId"] = paramType.id; + param1["value"] = color; + params.push(param1) + root.executeAction(params) + } + } + + touchDelegate: Rectangle { + height: 15 + width: height + radius: height / 2 + color: Material.accent + + + Rectangle { + color: colorPicker.hovered || colorPicker.pressed ? "#11000000" : "transparent" + anchors.centerIn: parent + height: 30 + width: height + radius: width / 2 + Behavior on color { ColorAnimation { duration: 200 } } + } + } + } + } + + Component { + id: colorPreviewComponent + Rectangle { + property var paramType: null + property var value: null + implicitHeight: app.mediumFont + implicitWidth: implicitHeight + color: value + radius: width / 4 + } + } + + Component { + id: buttonComponent + Button { + // just to suppress some warnings + property var paramType: null + property var value: null + text: "Do it" + onClicked: { + var params = []; + for (var i = 0; i < root.actionType.paramTypes.count; i++) { + var param = new Object(); + param["paramTypeId"] = root.actionType.paramTypes.get(i).id; + param["value"] = paramRepeater.itemAt(i).item.value; + params.push(param) + } + + root.executeAction(params) + } + } + } +} diff --git a/guh-control/ui/devicelistpages/GenericDeviceListPage.qml b/guh-control/ui/devicelistpages/GenericDeviceListPage.qml index e699a275..9cabffa6 100644 --- a/guh-control/ui/devicelistpages/GenericDeviceListPage.qml +++ b/guh-control/ui/devicelistpages/GenericDeviceListPage.qml @@ -25,7 +25,10 @@ Page { return qsTr("All my things") } - onBackPressed: pageStack.pop() + onBackPressed: { + print("popping") + pageStack.pop() + } } function enterPage(index, replace) { diff --git a/guh-control/ui/devicelistpages/LightsDeviceListPage.qml b/guh-control/ui/devicelistpages/LightsDeviceListPage.qml index 0583d901..48473422 100644 --- a/guh-control/ui/devicelistpages/LightsDeviceListPage.qml +++ b/guh-control/ui/devicelistpages/LightsDeviceListPage.qml @@ -61,6 +61,7 @@ Page { Label { Layout.fillWidth: true text: model.name + elide: Text.ElideRight verticalAlignment: Text.AlignVCenter } Slider { diff --git a/guh-control/ui/devicepages/ButtonDevicePage.qml b/guh-control/ui/devicepages/ButtonDevicePage.qml index 40ab87e2..e604a878 100644 --- a/guh-control/ui/devicepages/ButtonDevicePage.qml +++ b/guh-control/ui/devicepages/ButtonDevicePage.qml @@ -9,7 +9,10 @@ DevicePageBase { header: GuhHeader { text: device.name - onBackPressed: pageStack.pop() + onBackPressed: { + print("popping") + pageStack.pop() + } HeaderButton { imageSource: "../images/info.svg" @@ -34,11 +37,11 @@ DevicePageBase { model: RulesFilterModel { id: rulesFilterModel rules: Engine.ruleManager.rules - filterEventDeviceId: root.device.id + filterDeviceId: root.device.id } delegate: SwipeDelegate { width: parent.width - property var ruleActions: rulesFilterModel.get(index).ruleActions + property var ruleActions: rulesFilterModel.get(index).actions property var ruleAction: ruleActions.count == 1 ? ruleActions.get(0) : null property var ruleActionType: ruleAction ? ruleActionDeviceClass.actionTypes.getActionType(ruleAction.actionTypeId) : null property var ruleActionDevice: ruleAction ? Engine.deviceManager.devices.getDevice(ruleAction.deviceId) : null diff --git a/guh-control/ui/devicepages/ColorLightDevicePage.qml b/guh-control/ui/devicepages/ColorLightDevicePage.qml index 628b0005..761ac4e8 100644 --- a/guh-control/ui/devicepages/ColorLightDevicePage.qml +++ b/guh-control/ui/devicepages/ColorLightDevicePage.qml @@ -1,9 +1,9 @@ import QtQuick 2.5 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.1 +import QtQuick.Controls.Material 2.1 import Guh 1.0 import "../components" -import "../actiondelegates" DevicePageBase { id: root @@ -116,23 +116,55 @@ DevicePageBase { } - ActionDelegateColor { + ColorPicker { + id: colorPicker Layout.fillWidth: true Layout.fillHeight: true - actionType: root.deviceClass.actionTypes.findByName("color") - actionState: actionType ? root.device.states.getState(actionType.id).value : null + Layout.margins: app.margins + property var actionType: root.deviceClass.actionTypes.findByName("color") + property var actionState: actionType ? root.device.states.getState(actionType.id).value : null visible: root.deviceClass.interfaces.indexOf("colorlight") >= 0 - onExecuteAction: { - Engine.deviceManager.executeAction(root.device.id, actionType.id, params) + color: actionState ? actionState : "white" + touchDelegate: Rectangle { + height: 15 + width: height + radius: height / 2 + color: Material.accent + + Rectangle { + color: colorPicker.hovered || colorPicker.pressed ? "#11000000" : "transparent" + anchors.centerIn: parent + height: 30 + width: height + radius: width / 2 + Behavior on color { + ColorAnimation { + duration: 200 + } + } + } + } + + property var lastSentTime: new Date() + onColorChanged: { + var currentTime = new Date(); + if (pressed && currentTime - lastSentTime > 200) { + var params = []; + var param1 = new Object(); + param1["paramTypeId"] = actionType.paramTypes.get(0).id; + param1["value"] = color; + params.push(param1) + Engine.deviceManager.executeAction(root.device.id, actionType.id, params) + lastSentTime = currentTime + } } } Item { Layout.fillWidth: true Layout.fillHeight: true + Layout.preferredHeight: 0 } - } - } diff --git a/guh-control/ui/devicepages/GenericDevicePage.qml b/guh-control/ui/devicepages/GenericDevicePage.qml index 80fecea9..51f231e9 100644 --- a/guh-control/ui/devicepages/GenericDevicePage.qml +++ b/guh-control/ui/devicepages/GenericDevicePage.qml @@ -103,6 +103,7 @@ DevicePageBase { print("have actionType param:", actionType.paramTypes.get(i).name, actionType.paramTypes.get(i).type) } + return Qt.resolvedUrl("../actiondelegates-ng/ActionDelegate.qml"); var delegate = "ActionDelegateFallback.qml"; if (actionType.paramTypes.count === 0) { delegate = "ActionDelegateNoParams.qml"; @@ -117,7 +118,10 @@ DevicePageBase { } else if (paramType.type === "String" && paramType.allowedValues.length > 0) { delegate = "ActionDelegateStringFromStringList.qml"; } + } else { + } + return Qt.resolvedUrl("../actiondelegates/" + delegate); } @@ -136,7 +140,7 @@ DevicePageBase { Connections { target: delegateLoader.item ? delegateLoader.item : null onExecuteAction: { - delegateLoader.commandId = Engine.deviceManager.executeAction(root.device.id, model.id, params) + Engine.deviceManager.executeAction(root.device.id, model.id, params) } } Connections { diff --git a/guh-control/ui/magic/EditRulePage.qml b/guh-control/ui/magic/EditRulePage.qml index 20c0c6f9..7d96bb3e 100644 --- a/guh-control/ui/magic/EditRulePage.qml +++ b/guh-control/ui/magic/EditRulePage.qml @@ -13,6 +13,11 @@ Page { onAccept: busyOverlay.opacity = 1 + readonly property bool isStateBased: rule.eventDescriptors.count === 0 + readonly property bool actionsVisible: rule.eventDescriptors.count > 0 || rule.stateEvaluator !== null + readonly property bool exitActionsVisible: actionsVisible && isStateBased + readonly property bool hasExitActions: rule.exitActions.count > 0 + function addEventDescriptor() { var eventDescriptor = root.rule.eventDescriptors.createNewEventDescriptor(); var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml")); @@ -36,27 +41,51 @@ Page { }) } - function addRuleAction() { - var ruleAction = root.rule.ruleActions.createNewRuleAction(); + function editStateEvaluator() { + print("opening page", root.rule.stateEvaluator) + var page = pageStack.push(Qt.resolvedUrl("EditStateEvaluatorPage.qml"), { stateEvaluator: root.rule.stateEvaluator }) + } + + function addAction() { + var ruleAction = root.rule.actions.createNewRuleAction(); var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml")); page.onBackPressed.connect(function() { pageStack.pop() }) page.onThingSelected.connect(function(device) { + print("thing selected", device.name, device.id) ruleAction.deviceId = device.id; - selectRuleActionData(ruleAction) + selectRuleActionData(root.rule.actions, ruleAction) }) page.onInterfaceSelected.connect(function(interfaceName) { + print("interface selected", interfaceName) ruleAction.interfaceName = interfaceName; - selectRuleActionData(ruleAction) + selectRuleActionData(root.rule.actions, ruleAction) }) } - function selectRuleActionData(ruleAction) { + function addExitAction() { + var ruleAction = root.rule.exitActions.createNewRuleAction(); + var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml")); + page.onBackPressed.connect(function() { pageStack.pop() }) + page.onThingSelected.connect(function(device) { + print("thing selected", device.name, device.id) + ruleAction.deviceId = device.id; + selectRuleActionData(root.rule.exitActions, ruleAction) + }) + page.onInterfaceSelected.connect(function(interfaceName) { + print("interface selected", interfaceName) + ruleAction.interfaceName = interfaceName; + selectRuleActionData(root.rule.exitActions, ruleAction) + }) + } + + function selectRuleActionData(ruleActions, ruleAction) { + print("opening with ruleAction", ruleAction) var ruleActionPage = pageStack.push(Qt.resolvedUrl("SelectRuleActionPage.qml"), {text: "Select action", ruleAction: ruleAction }); ruleActionPage.onBackPressed.connect(function() { - ruleAction.destroy(); pageStack.pop(root); + ruleAction.destroy(); }) ruleActionPage.onDone.connect(function() { - root.rule.ruleActions.addRuleAction(ruleAction) + ruleActions.addRuleAction(ruleAction) pageStack.pop(root); }) } @@ -111,18 +140,19 @@ Page { } } - ThinDivider {} + ThinDivider { visible: !root.hasExitActions } Label { Layout.fillWidth: true Layout.margins: app.margins font.pixelSize: app.mediumFont text: "Events triggering this rule" + visible: !root.hasExitActions } Repeater { id: eventsRepeater - model: root.rule.eventDescriptors + model: root.hasExitActions ? null : root.rule.eventDescriptors delegate: SwipeDelegate { id: eventDelegate Layout.fillWidth: true @@ -174,9 +204,7 @@ Page { } } } - } - } swipe.right: MouseArea { height: eventDelegate.height @@ -198,24 +226,52 @@ Page { Layout.margins: app.margins text: eventsRepeater.count == 0 ? "Add an event..." : "Add another event..." onClicked: root.addEventDescriptor(); + visible: !root.hasExitActions } ThinDivider {} Label { - text: "Actions to execute" + text: "Conditions to be met" font.pixelSize: app.mediumFont Layout.fillWidth: true Layout.margins: app.margins } + StateEvaluatorDelegate { + Layout.fillWidth: true + stateEvaluator: root.rule.stateEvaluator + visible: root.rule.stateEvaluator !== null + } + + Button { + Layout.fillWidth: true + Layout.margins: app.margins + text: "Add a condition" + visible: root.rule.stateEvaluator === null + onClicked: { + root.rule.createStateEvaluator(); +// root.editStateEvaluator() + } + } + + ThinDivider { visible: root.actionsVisible } + + Label { + text: root.isStateBased ? "Active state enter actions" : "Actions to execute" + font.pixelSize: app.mediumFont + Layout.fillWidth: true + Layout.margins: app.margins + visible: root.actionsVisible + } + Repeater { id: actionsRepeater - model: root.rule.ruleActions + model: root.actionsVisible ? root.rule.actions : null delegate: SwipeDelegate { id: actionDelegate Layout.fillWidth: true - property var ruleAction: root.rule.ruleActions.get(index) + property var ruleAction: root.rule.actions.get(index) property var device: ruleAction.deviceId ? Engine.deviceManager.devices.getDevice(ruleAction.deviceId) : null property var iface: ruleAction.interfaceName ? Interfaces.findByName(ruleAction.interfaceName) : null property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null @@ -249,7 +305,7 @@ Page { name: "../images/delete.svg" color: "red" } - onClicked: root.rule.ruleActions.removeRuleAction(index) + onClicked: root.rule.actions.removeRuleAction(index) } } } @@ -258,7 +314,72 @@ Page { Layout.fillWidth: true Layout.margins: app.margins text: actionsRepeater.count == 0 ? "Add an action..." : "Add another action..." - onClicked: root.addRuleAction(); + onClicked: root.addAction(); + visible: root.actionsVisible + } + + ThinDivider { visible: root.exitActionsVisible } + + Label { + text: "Active state exit actions" + font.pixelSize: app.mediumFont + Layout.fillWidth: true + Layout.margins: app.margins + visible: root.exitActionsVisible + } + + + Repeater { + id: exitActionsRepeater + model: root.exitActionsVisible ? root.rule.exitActions : null + delegate: SwipeDelegate { + id: exitActionDelegate + Layout.fillWidth: true + property var ruleAction: root.rule.exitActions.get(index) + property var device: ruleAction.deviceId ? Engine.deviceManager.devices.getDevice(ruleAction.deviceId) : null + property var iface: ruleAction.interfaceName ? Interfaces.findByName(ruleAction.interfaceName) : null + property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null + property var actionType: deviceClass ? deviceClass.actionTypes.getActionType(ruleAction.actionTypeId) + : iface ? iface.actionTypes.findByName(ruleAction.interfaceAction) : null + contentItem: ColumnLayout { + Label { + Layout.fillWidth: true + text: qsTr("%1 - %2").arg(exitActionDelegate.device ? exitActionDelegate.device.name : exitActionDelegate.iface.displayName).arg(exitActionDelegate.actionType.displayName) + } + + RowLayout { + Layout.fillWidth: true + spacing: app.margins + Repeater { + model: exitActionDelegate.ruleAction.ruleActionParams + Label { + text: exitActionDelegate.actionType.paramTypes.getParamType(model.paramTypeId).displayName + " -> " + model.value + font.pixelSize: app.smallFont + } + } + } + } + swipe.right: MouseArea { + height: exitActionDelegate.height + width: height + anchors.right: parent.right + ColorIcon { + anchors.fill: parent + anchors.margins: app.margins + name: "../images/delete.svg" + color: "red" + } + onClicked: root.rule.exitActions.removeRuleAction(index) + } + } + } + + Button { + Layout.fillWidth: true + Layout.margins: app.margins + text: actionsRepeater.count == 0 ? "Add an action..." : "Add another action..." + onClicked: root.addExitAction(); + visible: root.exitActionsVisible } } } diff --git a/guh-control/ui/magic/EditStateEvaluatorPage.qml b/guh-control/ui/magic/EditStateEvaluatorPage.qml new file mode 100644 index 00000000..b14224fa --- /dev/null +++ b/guh-control/ui/magic/EditStateEvaluatorPage.qml @@ -0,0 +1,20 @@ +import QtQuick 2.8 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.2 +import Guh 1.0 +import "../components" + +Page { + id: root + header: GuhHeader { + text: "Conditions" + onBackPressed: pageStack.pop() + } + + property var stateEvaluator: null + + StateEvaluatorDelegate { + width: parent.width + stateEvaluator: root.stateEvaluator + } +} diff --git a/guh-control/ui/magic/SelectActionPage.qml b/guh-control/ui/magic/SelectActionPage.qml index 684b8048..069953f6 100644 --- a/guh-control/ui/magic/SelectActionPage.qml +++ b/guh-control/ui/magic/SelectActionPage.qml @@ -3,7 +3,6 @@ import QtQuick.Controls 2.1 import QtQuick.Layouts 1.1 import Guh 1.0 import "../components" -import "../actiondelegates" import "../paramdelegates" Page { diff --git a/guh-control/ui/magic/SelectRuleActionPage.qml b/guh-control/ui/magic/SelectRuleActionPage.qml index 46502f8c..35206a87 100644 --- a/guh-control/ui/magic/SelectRuleActionPage.qml +++ b/guh-control/ui/magic/SelectRuleActionPage.qml @@ -28,7 +28,7 @@ Page { HeaderButton { imageSource: header.interfacesMode ? "../images/view-expand.svg" : "../images/view-collapse.svg" - visible: root.ruleAction.interfaceName === "" + visible: root.ruleAction.deviceId || root.ruleAction.interfaceName === "" onClicked: header.interfacesMode = !header.interfacesMode } } @@ -50,8 +50,8 @@ Page { if (header.interfacesMode) { if (root.device) { for (var i = 0; i < Interfaces.count; i++) { - if (deviceClass.interfaces.indexOf(actionTemplateModel.get(i).interfaceName) >= 0) { - actualModel.append(actionTemplateModel.get(i)) + if (deviceClass.interfaces.indexOf(Interfaces.get(i).interfaceName) >= 0) { + actualModel.append(Interfaces.get(i)) } } } else if (root.ruleAction.interfaceName !== "") { diff --git a/guh-control/ui/magic/SelectRuleActionParamsPage.qml b/guh-control/ui/magic/SelectRuleActionParamsPage.qml index 3f8dc779..dd5ffba0 100644 --- a/guh-control/ui/magic/SelectRuleActionParamsPage.qml +++ b/guh-control/ui/magic/SelectRuleActionParamsPage.qml @@ -7,7 +7,7 @@ import Guh 1.0 Page { id: root - // Needs to be set and filled in with deviceId and actionTypeId + // Needs to be set and filled in with deviceId and actionTypeId or interfaceName and interfaceAction property var ruleAction readonly property var device: ruleAction && ruleAction.deviceId ? Engine.deviceManager.devices.getDevice(ruleAction.deviceId) : null diff --git a/guh-control/ui/magic/SelectStateDescriptorPage.qml b/guh-control/ui/magic/SelectStateDescriptorPage.qml new file mode 100644 index 00000000..0bdec708 --- /dev/null +++ b/guh-control/ui/magic/SelectStateDescriptorPage.qml @@ -0,0 +1,45 @@ +import QtQuick 2.4 +import QtQuick.Controls 2.1 +import "../components" +import Guh 1.0 + +Page { + id: root + property alias text: header.text + + // a ruleAction object needs to be set and prefilled with either deviceId or interfaceName + property var stateDescriptor: null + + readonly property var device: stateDescriptor && stateDescriptor.deviceId ? Engine.deviceManager.devices.getDevice(stateDescriptor.deviceId) : null + readonly property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null + + signal backPressed(); + signal done(); + + header: GuhHeader { + id: header + onBackPressed: root.backPressed(); + } + + ListView { + id: listView + anchors.fill: parent + model: root.deviceClass.stateTypes + + delegate: ItemDelegate { + text: model.displayName + width: parent.width + onClicked: { + var stateType = root.deviceClass.stateTypes.getStateType(model.id); + console.log("StateType", stateType.id, "selected.") + root.stateDescriptor.stateTypeId = stateType.id; + var paramsPage = pageStack.push(Qt.resolvedUrl("SelectStateDescriptorParamsPage.qml"), {stateDescriptor: root.stateDescriptor}) + paramsPage.onBackPressed.connect(function() { pageStack.pop(); }); + paramsPage.onCompleted.connect(function() { + pageStack.pop(); + root.done(); + }) + } + } + } +} diff --git a/guh-control/ui/magic/SelectStateDescriptorParamsPage.qml b/guh-control/ui/magic/SelectStateDescriptorParamsPage.qml new file mode 100644 index 00000000..cd6ca36e --- /dev/null +++ b/guh-control/ui/magic/SelectStateDescriptorParamsPage.qml @@ -0,0 +1,43 @@ +import QtQuick 2.8 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.2 +import "../components" +import "../paramdescriptordelegates" +import Guh 1.0 + +Page { + id: root + // Needs to be set and filled in with deviceId and eventTypeId + property var stateDescriptor: null + + readonly property var device: stateDescriptor && stateDescriptor.deviceId ? Engine.deviceManager.devices.getDevice(stateDescriptor.deviceId) : null + readonly property var stateType: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId).stateTypes.getStateType(stateDescriptor.stateTypeId) : null + + signal backPressed(); + signal completed(); + + header: GuhHeader { + text: "params" + onBackPressed: root.backPressed(); + } + + ColumnLayout { + anchors.fill: parent + ParamDescriptorDelegateBase { + id: paramDelegate + Layout.fillWidth: true + paramType: root.stateType + value: paramType.defaultValue + } + Button { + text: "OK" + Layout.fillWidth: true + Layout.margins: app.margins + onClicked: { + root.stateDescriptor.valueOperator = paramDelegate.operatorType + root.stateDescriptor.value = paramDelegate.value + root.completed() + } + } + } +} diff --git a/guh-control/ui/magic/SimpleStateEvaluatorDelegate.qml b/guh-control/ui/magic/SimpleStateEvaluatorDelegate.qml new file mode 100644 index 00000000..72c87e0a --- /dev/null +++ b/guh-control/ui/magic/SimpleStateEvaluatorDelegate.qml @@ -0,0 +1,83 @@ +import QtQuick 2.8 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.2 +import Guh 1.0 + +SwipeDelegate { + id: root + Layout.fillWidth: true + + property var stateEvaluator: null + property bool showChilds: false + + readonly property var device: stateEvaluator ? Engine.deviceManager.devices.getDevice(stateEvaluator.stateDescriptor.deviceId) : null + readonly property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null + readonly property var stateType: deviceClass ? deviceClass.stateTypes.getStateType(stateEvaluator.stateDescriptor.stateTypeId) : null + + Rectangle { + anchors.fill: parent + border.color: "black" + border.width: 1 + color: "transparent" + } + + contentItem: ColumnLayout { + Label { + Layout.fillWidth: true + property string operatorString: { + switch (root.stateEvaluator.stateDescriptor.valueOperator) { + case StateDescriptor.ValueOperatorEquals: + return "="; + case StateDescriptor.ValueOperatorNotEquals: + return "!="; + case StateDescriptor.ValueOperatorGreater: + return ">"; + case StateDescriptor.ValueOperatorGreaterOrEqual: + return ">="; + case StateDescriptor.ValueOperatorLess: + return "<"; + case StateDescriptor.ValueOperatorLessOrEqual: + return "<="; + } + return "FIXME" + } + + text: { + if (!root.device) { + return qsTr("Press to edit condition") + } + return qsTr("%1: %2 %3 %4").arg(root.device.name).arg(root.stateType.displayName).arg(operatorString).arg(root.stateEvaluator.stateDescriptor.value) + } + } + Repeater { + model: root.showChilds ? root.stateEvaluator.childEvaluators : null + delegate: Label { + Layout.fillWidth: true + property var stateEvaluator: root.stateEvaluator.childEvaluators.get(index) + property var stateDescriptor: stateEvaluator.stateDescriptor + readonly property var device: Engine.deviceManager.devices.getDevice(stateDescriptor.deviceId) + readonly property var deviceClass: Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) + readonly property var stateType: deviceClass.stateTypes.getStateType(stateDescriptor.stateTypeId) + + property string operatorString: { + switch (stateDescriptor.valueOperator) { + case StateDescriptor.ValueOperatorEquals: + return "="; + case StateDescriptor.ValueOperatorNotEquals: + return "!="; + case StateDescriptor.ValueOperatorGreater: + return ">"; + case StateDescriptor.ValueOperatorGreaterOrEqual: + return ">="; + case StateDescriptor.ValueOperatorLess: + return "<"; + case StateDescriptor.ValueOperatorLessOrEqual: + return "<="; + } + return "FIXME" + } + text: qsTr("%1 %2: %3 %4 %5%6").arg(root.stateEvaluator.stateOperator === StateEvaluator.StateOperatorAnd ? "and" : "or").arg(device.name).arg(stateType.displayName).arg(operatorString).arg(stateDescriptor.value).arg(stateEvaluator.childEvaluators.count > 0 ? "..." : "") + } + } + } +} diff --git a/guh-control/ui/magic/StateEvaluatorDelegate.qml b/guh-control/ui/magic/StateEvaluatorDelegate.qml new file mode 100644 index 00000000..373ad4d0 --- /dev/null +++ b/guh-control/ui/magic/StateEvaluatorDelegate.qml @@ -0,0 +1,61 @@ +import QtQuick 2.8 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.2 +import Guh 1.0 + +SwipeDelegate { + + id: root + property var stateEvaluator: null + readonly property var device: stateEvaluator ? Engine.deviceManager.devices.getDevice(stateEvaluator.stateDescriptor.deviceId) : null + readonly property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null + readonly property var stateType: deviceClass ? deviceClass.stateTypes.getStateType(stateEvaluator.stateDescriptor.stateTypeId) : null + + contentItem: ColumnLayout { + SimpleStateEvaluatorDelegate { + Layout.fillWidth: true + stateEvaluator: root.stateEvaluator + onClicked: { + var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml")); + page.backPressed.connect(function() {pageStack.pop()}) + page.thingSelected.connect(function(device) { + root.stateEvaluator.stateDescriptor.deviceId = device.id + var statePage = pageStack.push(Qt.resolvedUrl("SelectStateDescriptorPage.qml"), {text: "Select state", stateDescriptor: root.stateEvaluator.stateDescriptor}) + statePage.backPressed.connect(function() {pageStack.pop()}) + statePage.done.connect(function() {pageStack.pop(); pageStack.pop()}) + }) + } + } + + ComboBox { + Layout.fillWidth: true + model: ["and all of those", "or any of those"] + currentIndex: root.stateEvaluator.stateOperator === StateEvaluator.StateOperatorAnd ? 0 : 1 + visible: root.stateEvaluator.childEvaluators.count > 0 + onActivated: { + root.stateEvaluator.stateOperator = index == 0 ? StateEvaluator.StateOperatorAnd : StateEvaluator.StateOperatorOr + } + } + + Repeater { + model: root.stateEvaluator.childEvaluators + delegate: SimpleStateEvaluatorDelegate { + Layout.fillWidth: true + stateEvaluator: root.stateEvaluator.childEvaluators.get(index) + showChilds: true + onClicked: { + pageStack.push(Qt.resolvedUrl("EditStateEvaluatorPage.qml"), {stateEvaluator: stateEvaluator}) + } + } + } + + Button { + Layout.fillWidth: true + text: "Add a condition" + onClicked: { + root.stateEvaluator.addChildEvaluator() + // root.editStateEvaluator() + } + } + } +} diff --git a/guh-control/ui/system/LogViewerPage.qml b/guh-control/ui/system/LogViewerPage.qml index 07ee7e9f..975dbec5 100644 --- a/guh-control/ui/system/LogViewerPage.qml +++ b/guh-control/ui/system/LogViewerPage.qml @@ -75,7 +75,7 @@ Page { Label { width: listView.column2Width - text: "Device" + text: "Thing" } Label { width: listView.column3Width @@ -134,7 +134,7 @@ Page { Label { width: listView.column2Width - text: delegate.device.name + text: model.source === LogEntry.LoggingSourceSystem ? "Nymea Server" : delegate.device.name elide: Text.ElideRight } Label { @@ -144,7 +144,7 @@ Page { case LogEntry.LoggingSourceStates: return delegate.deviceClass.stateTypes.getStateType(model.typeId).displayName; case LogEntry.LoggingSourceSystem: - return "SYS"; + return model.loggingEventType === LogEntry.LoggingEventTypeActiveChange ? "Active changed" : "FIXME" case LogEntry.LoggingSourceActions: return delegate.deviceClass.actionTypes.getActionType(model.typeId).displayName; case LogEntry.LoggingSourceEvents: diff --git a/libguh-common/types/paramtypes.cpp b/libguh-common/types/paramtypes.cpp index e5dcd348..9060a339 100644 --- a/libguh-common/types/paramtypes.cpp +++ b/libguh-common/types/paramtypes.cpp @@ -34,7 +34,10 @@ QList ParamTypes::paramTypes() ParamType *ParamTypes::get(int index) const { - return m_paramTypes.at(index); + if (index >= 0 && index < m_paramTypes.count()) { + return m_paramTypes.at(index); + } + return nullptr; } ParamType *ParamTypes::getParamType(const QString &id) const diff --git a/libguh-common/types/rule.cpp b/libguh-common/types/rule.cpp index b43f2a27..f14a2290 100644 --- a/libguh-common/types/rule.cpp +++ b/libguh-common/types/rule.cpp @@ -11,8 +11,9 @@ Rule::Rule(const QUuid &id, QObject *parent) : QObject(parent), m_id(id), m_eventDescriptors(new EventDescriptors(this)), - m_stateEvaluator(new StateEvaluator(this)), - m_ruleActions(new RuleActions(this)) +// m_stateEvaluator(new StateEvaluator(this)), + m_actions(new RuleActions(this)), + m_exitActions(new RuleActions(this)) { } @@ -71,9 +72,29 @@ StateEvaluator *Rule::stateEvaluator() const return m_stateEvaluator; } -RuleActions *Rule::ruleActions() const +RuleActions *Rule::actions() const { - return m_ruleActions; + return m_actions; +} + +RuleActions *Rule::exitActions() const +{ + return m_exitActions; +} + +void Rule::setStateEvaluator(StateEvaluator *stateEvaluator) +{ + if (m_stateEvaluator) { + m_stateEvaluator->deleteLater(); + } + m_stateEvaluator = stateEvaluator; + m_stateEvaluator->setParent(this); + emit stateEvaluatorChanged(); +} + +void Rule::createStateEvaluator() +{ + setStateEvaluator(new StateEvaluator(this)); } Rule *Rule::clone() const @@ -89,8 +110,11 @@ Rule *Rule::clone() const // for (int i = 0; i < this->stateEvaluator()->childEvaluators()->rowCount(); i++) { // ret->stateEvaluator()->childEvaluators()-> // } - for (int i = 0; i < this->ruleActions()->rowCount(); i++) { - ret->ruleActions()->addRuleAction(this->ruleActions()->get(i)->clone()); + for (int i = 0; i < this->actions()->rowCount(); i++) { + ret->actions()->addRuleAction(this->actions()->get(i)->clone()); + } + for (int i = 0; i < this->exitActions()->rowCount(); i++) { + ret->exitActions()->addRuleAction(this->exitActions()->get(i)->clone()); } return ret; } diff --git a/libguh-common/types/rule.h b/libguh-common/types/rule.h index 759650a4..cdfd959d 100644 --- a/libguh-common/types/rule.h +++ b/libguh-common/types/rule.h @@ -16,8 +16,9 @@ class Rule : public QObject Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) Q_PROPERTY(bool active READ active NOTIFY activeChanged) Q_PROPERTY(EventDescriptors* eventDescriptors READ eventDescriptors CONSTANT) - Q_PROPERTY(StateEvaluator* stateEvaluator READ stateEvaluator CONSTANT) - Q_PROPERTY(RuleActions* ruleActions READ ruleActions CONSTANT) + Q_PROPERTY(StateEvaluator* stateEvaluator READ stateEvaluator NOTIFY stateEvaluatorChanged) + Q_PROPERTY(RuleActions* actions READ actions CONSTANT) + Q_PROPERTY(RuleActions* exitActions READ exitActions CONSTANT) public: explicit Rule(const QUuid &id = QUuid(), QObject *parent = nullptr); @@ -34,7 +35,12 @@ public: EventDescriptors* eventDescriptors() const; StateEvaluator *stateEvaluator() const; - RuleActions* ruleActions() const; + RuleActions* actions() const; + RuleActions* exitActions() const; + + void setStateEvaluator(StateEvaluator* stateEvaluator); + + Q_INVOKABLE void createStateEvaluator(); Rule *clone() const; @@ -42,6 +48,7 @@ signals: void nameChanged(); void enabledChanged(); void activeChanged(); + void stateEvaluatorChanged(); private: QUuid m_id; @@ -50,7 +57,8 @@ private: bool m_active = false; EventDescriptors *m_eventDescriptors = nullptr; StateEvaluator *m_stateEvaluator = nullptr; - RuleActions *m_ruleActions = nullptr; + RuleActions *m_actions = nullptr; + RuleActions *m_exitActions = nullptr; }; #endif // RULE_H diff --git a/libguh-common/types/statedescriptor.cpp b/libguh-common/types/statedescriptor.cpp index 7e60ab3f..104e17c1 100644 --- a/libguh-common/types/statedescriptor.cpp +++ b/libguh-common/types/statedescriptor.cpp @@ -10,26 +10,63 @@ StateDescriptor::StateDescriptor(const QUuid &deviceId, StateDescriptor::ValueOp } +StateDescriptor::StateDescriptor(QObject *parent) : QObject(parent) +{ + +} + QUuid StateDescriptor::deviceId() const { return m_deviceId; } +void StateDescriptor::setDeviceId(const QUuid &deviceId) +{ + if (m_deviceId != deviceId) { + m_deviceId = deviceId; + emit deviceIdChanged(); + } +} + StateDescriptor::ValueOperator StateDescriptor::valueOperator() const { return m_operator; } +void StateDescriptor::setValueOperator(StateDescriptor::ValueOperator valueOperator) +{ + if (m_operator != valueOperator) { + m_operator = valueOperator; + emit valueOperatorChanged(); + } +} + QUuid StateDescriptor::stateTypeId() const { return m_stateTypeId; } +void StateDescriptor::setStateTypeId(const QUuid &stateTypeId) +{ + if (m_stateTypeId != stateTypeId) { + m_stateTypeId = stateTypeId; + emit stateTypeIdChanged(); + } +} + QVariant StateDescriptor::value() const { return m_value; } +void StateDescriptor::setValue(const QVariant &value) +{ + if (m_value != value) { + m_value = value; + emit valueChanged(); + } +} + StateDescriptor *StateDescriptor::clone() const { StateDescriptor *ret = new StateDescriptor(deviceId(), valueOperator(), stateTypeId(), value()); diff --git a/libguh-common/types/statedescriptor.h b/libguh-common/types/statedescriptor.h index 7527b686..34e726bd 100644 --- a/libguh-common/types/statedescriptor.h +++ b/libguh-common/types/statedescriptor.h @@ -8,10 +8,10 @@ class StateDescriptor : public QObject { Q_OBJECT - Q_PROPERTY(QUuid deviceId READ deviceId CONSTANT) - Q_PROPERTY(ValueOperator valueOperator READ valueOperator CONSTANT) - Q_PROPERTY(QUuid stateTypeId READ stateTypeId CONSTANT) - Q_PROPERTY(QVariant value READ value CONSTANT) + Q_PROPERTY(QUuid deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) + Q_PROPERTY(ValueOperator valueOperator READ valueOperator WRITE setValueOperator NOTIFY valueOperatorChanged) + Q_PROPERTY(QUuid stateTypeId READ stateTypeId WRITE setStateTypeId NOTIFY stateTypeIdChanged) + Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) public: enum ValueOperator { @@ -25,13 +25,28 @@ public: Q_ENUM(ValueOperator) explicit StateDescriptor(const QUuid &deviceId, ValueOperator valueOperator, const QUuid &stateTypeId, const QVariant &value, QObject *parent = nullptr); + StateDescriptor(QObject *parent = nullptr); QUuid deviceId() const; + void setDeviceId(const QUuid &deviceId); + ValueOperator valueOperator() const; + void setValueOperator(ValueOperator valueOperator); + QUuid stateTypeId() const; + void setStateTypeId(const QUuid &stateTypeId); + QVariant value() const; + void setValue(const QVariant &value); StateDescriptor* clone() const; + +signals: + void deviceIdChanged(); + void valueOperatorChanged(); + void stateTypeIdChanged(); + void valueChanged(); + private: QUuid m_deviceId; ValueOperator m_operator = ValueOperatorEquals; diff --git a/libguh-common/types/stateevaluator.cpp b/libguh-common/types/stateevaluator.cpp index bd665b3e..716d0294 100644 --- a/libguh-common/types/stateevaluator.cpp +++ b/libguh-common/types/stateevaluator.cpp @@ -5,7 +5,7 @@ StateEvaluator::StateEvaluator(QObject *parent) : QObject(parent) { m_childEvaluators = new StateEvaluators(this); -// m_stateDescriptor = new StateDescriptor(this); + m_stateDescriptor = new StateDescriptor(this); } StateEvaluator::StateOperator StateEvaluator::stateOperator() const @@ -15,7 +15,10 @@ StateEvaluator::StateOperator StateEvaluator::stateOperator() const void StateEvaluator::setStateOperator(StateEvaluator::StateOperator stateOperator) { - m_operator = stateOperator; + if (m_operator != stateOperator) { + m_operator = stateOperator; + emit stateOperatorChanged(); + } } StateEvaluators *StateEvaluator::childEvaluators() const @@ -49,3 +52,10 @@ bool StateEvaluator::containsDevice(const QUuid &deviceId) const } return false; } + +StateEvaluator* StateEvaluator::addChildEvaluator() +{ + StateEvaluator* stateEvaluator = new StateEvaluator(m_childEvaluators); + m_childEvaluators->addStateEvaluator(stateEvaluator); + return stateEvaluator; +} diff --git a/libguh-common/types/stateevaluator.h b/libguh-common/types/stateevaluator.h index 77de70f8..437f6d08 100644 --- a/libguh-common/types/stateevaluator.h +++ b/libguh-common/types/stateevaluator.h @@ -9,7 +9,7 @@ class StateDescriptor; class StateEvaluator : public QObject { Q_OBJECT - Q_PROPERTY(StateOperator stateOperator READ stateOperator CONSTANT) + Q_PROPERTY(StateOperator stateOperator READ stateOperator WRITE setStateOperator NOTIFY stateOperatorChanged) Q_PROPERTY(StateEvaluators* childEvaluators READ childEvaluators CONSTANT) Q_PROPERTY(StateDescriptor* stateDescriptor READ stateDescriptor CONSTANT) @@ -31,6 +31,11 @@ public: bool containsDevice(const QUuid &deviceId) const; + Q_INVOKABLE StateEvaluator* addChildEvaluator(); + +signals: + void stateOperatorChanged(); + private: StateOperator m_operator = StateOperatorAnd; StateEvaluators *m_childEvaluators = nullptr; diff --git a/libguh-common/types/stateevaluators.cpp b/libguh-common/types/stateevaluators.cpp index 9cf83052..73e15693 100644 --- a/libguh-common/types/stateevaluators.cpp +++ b/libguh-common/types/stateevaluators.cpp @@ -22,7 +22,23 @@ QHash StateEvaluators::roleNames() const return roles; } +void StateEvaluators::addStateEvaluator(StateEvaluator *stateEvaluator) +{ + beginInsertRows(QModelIndex(), m_list.count(), m_list.count()); + m_list.append(stateEvaluator); + endInsertRows(); + emit countChanged(); +} + StateEvaluator *StateEvaluators::get(int index) const { return m_list.at(index); } + +StateEvaluator *StateEvaluators::take(int index) +{ + beginRemoveRows(QModelIndex(), index, index); + return m_list.takeAt(index); + endInsertRows(); + emit countChanged(); +} diff --git a/libguh-common/types/stateevaluators.h b/libguh-common/types/stateevaluators.h index d2e3094f..ec64681d 100644 --- a/libguh-common/types/stateevaluators.h +++ b/libguh-common/types/stateevaluators.h @@ -8,6 +8,8 @@ class StateEvaluator; class StateEvaluators : public QAbstractListModel { Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + public: explicit StateEvaluators(QObject *parent = nullptr); @@ -15,7 +17,13 @@ public: QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; - StateEvaluator* get(int index) const; + void addStateEvaluator(StateEvaluator* stateEvaluator); + Q_INVOKABLE StateEvaluator* get(int index) const; + StateEvaluator* take(int index); + +signals: + void countChanged(); + private: QList m_list; };