add support for tagging

rework the main page completely by using the new features available for tagging
This commit is contained in:
Michael Zanetti 2018-06-28 10:34:52 +02:00
parent 37c94e083b
commit 113963a77b
55 changed files with 2393 additions and 883 deletions

View File

@ -22,11 +22,12 @@
#include "devicesproxy.h"
#include "engine.h"
#include "tagsmanager.h"
DevicesProxy::DevicesProxy(QObject *parent) :
QSortFilterProxyModel(parent)
{
connect(Engine::instance()->tagsManager()->tags(), &Tags::countChanged, this, &DevicesProxy::invalidateFilter);
}
Devices *DevicesProxy::devices() const
@ -47,31 +48,46 @@ void DevicesProxy::setDevices(Devices *devices)
}
}
DeviceClass::BasicTag DevicesProxy::filterTag() const
QString DevicesProxy::filterTagId() const
{
return m_filterTag;
return m_filterTagId;
}
void DevicesProxy::setFilterTag(DeviceClass::BasicTag filterTag)
void DevicesProxy::setFilterTagId(const QString &filterTag)
{
if (m_filterTag != filterTag) {
m_filterTag = filterTag;
emit filterTagChanged();
if (m_filterTagId != filterTagId()) {
m_filterTagId = filterTag;
emit filterTagIdChanged();
invalidateFilter();
emit countChanged();
}
}
QString DevicesProxy::filterInterface() const
QStringList DevicesProxy::shownInterfaces() const
{
return m_filterInterface;
return m_shownInterfaces;
}
void DevicesProxy::setFilterInterface(const QString &filterInterface)
void DevicesProxy::setShownInterfaces(const QStringList &shownInterfaces)
{
if (m_filterInterface != filterInterface) {
m_filterInterface = filterInterface;
emit filterInterfaceChanged();
if (m_shownInterfaces != shownInterfaces) {
m_shownInterfaces = shownInterfaces;
emit shownInterfacesChanged();
invalidateFilter();
emit countChanged();
}
}
QStringList DevicesProxy::hiddenInterfaces() const
{
return m_hiddenInterfaces;
}
void DevicesProxy::setHiddenInterfaces(const QStringList &hiddenInterfaces)
{
if (m_hiddenInterfaces != hiddenInterfaces) {
m_hiddenInterfaces = hiddenInterfaces;
emit hiddenInterfacesChanged();
invalidateFilter();
emit countChanged();
}
@ -92,17 +108,33 @@ bool DevicesProxy::lessThan(const QModelIndex &left, const QModelIndex &right) c
bool DevicesProxy::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if (m_filterTag != DeviceClass::BasicTagNone) {
QList<DeviceClass::BasicTag> tags = Engine::instance()->deviceManager()->deviceClasses()->getDeviceClass(m_devices->get(source_row)->deviceClassId())->basicTags();
if (!tags.contains(m_filterTag)) {
Device *device = m_devices->get(source_row);
if (!m_filterTagId.isEmpty()) {
if (!Engine::instance()->tagsManager()->tags()->findDeviceTag(device->id().toString(), m_filterTagId)) {
return false;
}
}
if (!m_filterInterface.isEmpty()) {
if (!m_shownInterfaces.isEmpty()) {
QStringList interfaces = Engine::instance()->deviceManager()->deviceClasses()->getDeviceClass(m_devices->get(source_row)->deviceClassId())->interfaces();
if (!interfaces.contains(m_filterInterface)) {
bool foundMatch = false;
foreach (const QString &filterInterface, m_shownInterfaces) {
if (interfaces.contains(filterInterface)) {
foundMatch = true;
continue;
}
}
if (!foundMatch) {
return false;
}
}
if (!m_hiddenInterfaces.isEmpty()) {
QStringList interfaces = Engine::instance()->deviceManager()->deviceClasses()->getDeviceClass(m_devices->get(source_row)->deviceClassId())->interfaces();
foreach (const QString &filterInterface, m_hiddenInterfaces) {
if (interfaces.contains(filterInterface)) {
return false;
}
}
}
return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
}

View File

@ -34,8 +34,9 @@ class DevicesProxy : public QSortFilterProxyModel
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
Q_PROPERTY(Devices *devices READ devices WRITE setDevices NOTIFY devicesChanged)
Q_PROPERTY(DeviceClass::BasicTag filterTag READ filterTag WRITE setFilterTag NOTIFY filterTagChanged)
Q_PROPERTY(QString filterInterface READ filterInterface WRITE setFilterInterface NOTIFY filterInterfaceChanged)
Q_PROPERTY(QString filterTagId READ filterTagId WRITE setFilterTagId NOTIFY filterTagIdChanged)
Q_PROPERTY(QStringList shownInterfaces READ shownInterfaces WRITE setShownInterfaces NOTIFY shownInterfacesChanged)
Q_PROPERTY(QStringList hiddenInterfaces READ hiddenInterfaces WRITE setHiddenInterfaces NOTIFY hiddenInterfacesChanged)
public:
explicit DevicesProxy(QObject *parent = 0);
@ -43,24 +44,29 @@ public:
Devices *devices() const;
void setDevices(Devices *devices);
DeviceClass::BasicTag filterTag() const;
void setFilterTag(DeviceClass::BasicTag filterTag);
QString filterTagId() const;
void setFilterTagId(const QString &filterTag);
QString filterInterface() const;
void setFilterInterface(const QString &filterInterface);
QStringList shownInterfaces() const;
void setShownInterfaces(const QStringList &shownInterfaces);
QStringList hiddenInterfaces() const;
void setHiddenInterfaces(const QStringList &hiddenInterfaces);
Q_INVOKABLE Device *get(int index) const;
signals:
void devicesChanged();
void filterTagChanged();
void filterInterfaceChanged();
void filterTagIdChanged();
void shownInterfacesChanged();
void hiddenInterfacesChanged();
void countChanged();
private:
Devices *m_devices = nullptr;
DeviceClass::BasicTag m_filterTag = DeviceClass::BasicTagNone;
QString m_filterInterface;
QString m_filterTagId;
QStringList m_shownInterfaces;
QStringList m_hiddenInterfaces;
protected:
bool lessThan(const QModelIndex &left, const QModelIndex &right) const Q_DECL_OVERRIDE;

View File

@ -44,7 +44,7 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
if (!entry.name().startsWith("nymea") || entry.ip().isNull()) {
return;
}
qDebug() << "zeroconf service discovered" << entry << entry.txt() << entry.type();
// qDebug() << "zeroconf service discovered" << entry << entry.txt() << entry.type();
QString uuid;
bool sslEnabled = false;
@ -65,14 +65,14 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
version = txtRecord.second;
}
}
qDebug() << "avahi service entry added" << serverName << uuid << sslEnabled;
// qDebug() << "avahi service entry added" << serverName << uuid << sslEnabled;
DiscoveryDevice* device = m_discoveryModel->find(uuid);
if (!device) {
device = new DiscoveryDevice(m_discoveryModel);
device->setUuid(uuid);
qDebug() << "Adding new host to model";
// qDebug() << "Adding new host to model";
m_discoveryModel->addDevice(device);
}
device->setHostAddress(entry.ip());
@ -80,7 +80,7 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
device->setVersion(version);
PortConfig *portConfig = device->portConfigs()->find(entry.port());
if (!portConfig) {
qDebug() << "Adding new port config";
// qDebug() << "Adding new port config";
portConfig = new PortConfig(entry.port());
device->portConfigs()->insert(portConfig);
}

View File

@ -23,6 +23,7 @@
#include "tcpsocketinterface.h"
#include "rulemanager.h"
#include "logmanager.h"
#include "tagsmanager.h"
#include "basicconfiguration.h"
Engine* Engine::s_instance = 0;
@ -45,6 +46,11 @@ RuleManager *Engine::ruleManager() const
return m_ruleManager;
}
TagsManager *Engine::tagsManager() const
{
return m_tagsManager;
}
JsonRpcClient *Engine::jsonRpcClient() const
{
return m_jsonRpcClient;
@ -77,11 +83,14 @@ Engine::Engine(QObject *parent) :
m_deviceManager(new DeviceManager(m_jsonRpcClient, this)),
m_ruleManager(new RuleManager(m_jsonRpcClient, this)),
m_logManager(new LogManager(m_jsonRpcClient, this)),
m_tagsManager(new TagsManager(m_jsonRpcClient, this)),
m_basicConfiguration(new BasicConfiguration(m_jsonRpcClient, this)),
m_bluetoothDiscovery(new BluetoothDiscovery(this))
{
connect(m_jsonRpcClient, &JsonRpcClient::connectedChanged, this, &Engine::onConnectedChanged);
connect(m_jsonRpcClient, &JsonRpcClient::authenticationRequiredChanged, this, &Engine::onConnectedChanged);
connect(m_deviceManager, &DeviceManager::fetchingDataChanged, this, &Engine::onDeviceManagerFetchingChanged);
}
void Engine::onConnectedChanged()
@ -98,3 +107,10 @@ void Engine::onConnectedChanged()
}
}
}
void Engine::onDeviceManagerFetchingChanged()
{
if (!m_deviceManager->fetchingData()) {
m_tagsManager->init();
}
}

View File

@ -30,6 +30,7 @@
class RuleManager;
class LogManager;
class TagsManager;
class BasicConfiguration;
class Engine : public QObject
@ -38,6 +39,7 @@ class Engine : public QObject
Q_PROPERTY(NymeaConnection* connection READ connection CONSTANT)
Q_PROPERTY(DeviceManager* deviceManager READ deviceManager CONSTANT)
Q_PROPERTY(RuleManager* ruleManager READ ruleManager CONSTANT)
Q_PROPERTY(TagsManager* tagsManager READ tagsManager CONSTANT)
Q_PROPERTY(JsonRpcClient* jsonRpcClient READ jsonRpcClient CONSTANT)
Q_PROPERTY(BasicConfiguration* basicConfiguration READ basicConfiguration CONSTANT)
Q_PROPERTY(BluetoothDiscovery* bluetoothDiscovery READ bluetoothDiscovery CONSTANT)
@ -51,6 +53,7 @@ public:
NymeaConnection *connection() const;
DeviceManager *deviceManager() const;
RuleManager *ruleManager() const;
TagsManager *tagsManager() const;
JsonRpcClient *jsonRpcClient() const;
LogManager *logManager() const;
BasicConfiguration *basicConfiguration() const;
@ -65,11 +68,13 @@ private:
DeviceManager *m_deviceManager;
RuleManager *m_ruleManager;
LogManager *m_logManager;
TagsManager *m_tagsManager;
BasicConfiguration *m_basicConfiguration;
BluetoothDiscovery *m_bluetoothDiscovery;
private slots:
void onConnectedChanged();
void onDeviceManagerFetchingChanged();
};

View File

@ -61,6 +61,20 @@ void InterfacesModel::setShownInterfaces(const QStringList &shownInterfaces)
}
}
bool InterfacesModel::showUncategorized() const
{
return m_showUncategorized;
}
void InterfacesModel::setShowUncategorized(bool showUncategorized)
{
if (m_showUncategorized != showUncategorized) {
m_showUncategorized = showUncategorized;
emit showUncategorizedChanged();
syncInterfaces();
}
}
void InterfacesModel::syncInterfaces()
{
if (!m_devices) {
@ -72,6 +86,7 @@ void InterfacesModel::syncInterfaces()
DeviceClass *dc = Engine::instance()->deviceManager()->deviceClasses()->getDeviceClass(m_devices->get(i)->deviceClassId());
// qDebug() << "device" <<dc->name() << "has interfaces" << dc->interfaces();
bool isInShownIfaces = false;
foreach (const QString &interface, dc->interfaces()) {
if (!m_shownInterfaces.contains(interface)) {
continue;
@ -80,6 +95,10 @@ void InterfacesModel::syncInterfaces()
if (!interfacesInSource.contains(interface)) {
interfacesInSource.append(interface);
}
isInShownIfaces = true;
}
if (!isInShownIfaces && !interfacesInSource.contains("uncategorized")) {
interfacesInSource.append("uncategorized");
}
}
QStringList interfacesToAdd = interfacesInSource;
@ -113,3 +132,38 @@ void InterfacesModel::rowsChanged(const QModelIndex &index, int first, int last)
syncInterfaces();
}
InterfacesSortModel::InterfacesSortModel(QObject *parent):
QSortFilterProxyModel(parent)
{
}
InterfacesModel *InterfacesSortModel::interfacesModel() const
{
return m_interfacesModel;
}
void InterfacesSortModel::setInterfacesModel(InterfacesModel *interfacesModel)
{
if (m_interfacesModel != interfacesModel) {
m_interfacesModel = interfacesModel;
setSourceModel(interfacesModel);
setSortRole(Devices::RoleName);
sort(0);
emit interfacesModelChanged();
}
}
bool InterfacesSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
QVariant leftName = sourceModel()->data(left, InterfacesModel::RoleName);
QVariant rightName = sourceModel()->data(right, InterfacesModel::RoleName);
if (leftName == "uncategorized") {
return false;
}
if (rightName == "uncategorized") {
return true;
}
return m_interfacesModel->shownInterfaces().indexOf(leftName.toString()) < m_interfacesModel->shownInterfaces().indexOf(rightName.toString());
}

View File

@ -12,6 +12,7 @@ class InterfacesModel : public QAbstractListModel
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
Q_PROPERTY(Devices* devices READ devices WRITE setDevices NOTIFY devicesChanged)
Q_PROPERTY(QStringList shownInterfaces READ shownInterfaces WRITE setShownInterfaces NOTIFY shownInterfacesChanged)
Q_PROPERTY(bool showUncategorized READ showUncategorized WRITE setShowUncategorized NOTIFY showUncategorizedChanged)
public:
enum Roles {
@ -31,10 +32,14 @@ public:
QStringList shownInterfaces() const;
void setShownInterfaces(const QStringList &shownInterfaces);
bool showUncategorized() const;
void setShowUncategorized(bool showUncategorized);
signals:
void countChanged();
void devicesChanged();
void shownInterfacesChanged();
void showUncategorizedChanged();
private slots:
void syncInterfaces();
@ -45,6 +50,27 @@ private:
QStringList m_interfaces;
QStringList m_shownInterfaces;
bool m_showUncategorized = false;
};
class InterfacesSortModel: public QSortFilterProxyModel
{
Q_OBJECT
Q_PROPERTY(InterfacesModel* interfacesModel READ interfacesModel WRITE setInterfacesModel NOTIFY interfacesModelChanged)
public:
InterfacesSortModel(QObject *parent = nullptr);
InterfacesModel* interfacesModel() const;
void setInterfacesModel(InterfacesModel* interfacesModel);
bool lessThan(const QModelIndex &left, const QModelIndex &right) const Q_DECL_OVERRIDE;
signals:
void interfacesModelChanged();
private:
InterfacesModel* m_interfacesModel = nullptr;
};
#endif // INTERFACESMODEL_H

View File

@ -239,6 +239,7 @@ QVariantMap JsonTypes::packRule(Rule *rule)
}
ret.insert("name", rule->name());
ret.insert("enabled", rule->enabled());
ret.insert("executable", rule->executable());
if (rule->actions()->rowCount() > 0) {
ret.insert("actions", packRuleActions(rule->actions()));

View File

@ -37,6 +37,9 @@
#include "models/interfacesproxy.h"
#include "basicconfiguration.h"
#include "wifisetup/networkmanagercontroler.h"
#include "tagsmanager.h"
#include "models/tagsproxymodel.h"
#include "types/tag.h"
static QObject* interfacesModel_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
@ -87,6 +90,7 @@ void registerQmlTypes() {
qmlRegisterUncreatableType<Devices>(uri, 1, 0, "Devices", "Can't create this in QML. Get it from the DeviceManager.");
qmlRegisterType<DevicesProxy>(uri, 1, 0, "DevicesProxy");
qmlRegisterType<InterfacesModel>(uri, 1, 0, "InterfacesModel");
qmlRegisterType<InterfacesSortModel>(uri, 1, 0, "InterfacesSortModel");
qmlRegisterUncreatableType<DeviceClass>(uri, 1, 0, "DeviceClass", "Can't create this in QML. Get it from the DeviceClasses.");
qmlRegisterUncreatableType<DeviceClasses>(uri, 1, 0, "DeviceClasses", "Can't create this in QML. Get it from the DeviceManager.");
@ -141,6 +145,11 @@ void registerQmlTypes() {
qmlRegisterType<ValueLogsProxyModel>(uri, 1, 0, "ValueLogsProxyModel");
qmlRegisterUncreatableType<LogEntry>(uri, 1, 0, "LogEntry", "Get them from LogsModel");
qmlRegisterUncreatableType<TagsManager>(uri, 1, 0, "TagsManager", "Get it from Engine");
qmlRegisterUncreatableType<Tags>(uri, 1, 0, "Tags", "Get it from TagsManager");
qmlRegisterUncreatableType<Tag>(uri, 1, 0, "Tag", "Get it from Tags");
qmlRegisterType<TagsProxyModel>(uri, 1, 0, "TagsProxyModel");
qmlRegisterType<NetworkManagerControler>(uri, 1, 0, "NetworkManagerControler");
qmlRegisterUncreatableType<BluetoothDiscovery>(uri, 1, 0, "BluetoothDiscovery", "Can't create this in QML. Get it from the Engine instance.");
qmlRegisterUncreatableType<BluetoothDeviceInfo>(uri, 1, 0, "BluetoothDeviceInfo", "Can't create this in QML. Get it from the DeviceInfos.");

View File

@ -12,7 +12,7 @@ include(../config.pri)
}
QT -= gui
QT += websockets bluetooth
QT += network websockets bluetooth
INCLUDEPATH += $$top_srcdir/libnymea-common $$top_srcdir/QtZeroConf
@ -57,7 +57,9 @@ SOURCES += \
wifisetup/wirelesssetupmanager.cpp \
wifisetup/networkmanagercontroler.cpp \
models/logsmodelng.cpp \
models/interfacesproxy.cpp
models/interfacesproxy.cpp \
tagsmanager.cpp \
models/tagsproxymodel.cpp
HEADERS += \
engine.h \
@ -101,7 +103,9 @@ HEADERS += \
wifisetup/networkmanagercontroler.h \
libnymea-app-core.h \
models/logsmodelng.h \
models/interfacesproxy.h
models/interfacesproxy.h \
tagsmanager.h \
models/tagsproxymodel.h
unix {
target.path = /usr/lib

View File

@ -11,7 +11,7 @@
RulesFilterModel::RulesFilterModel(QObject *parent) : QSortFilterProxyModel(parent)
{
setSortRole(Rules::RoleName);
}
Rules *RulesFilterModel::rules() const
@ -27,6 +27,7 @@ void RulesFilterModel::setRules(Rules *rules)
emit rulesChanged();
invalidateFilter();
emit countChanged();
sort(0);
}
}
@ -45,6 +46,21 @@ void RulesFilterModel::setFilterDeviceId(const QString &filterDeviceId)
}
}
bool RulesFilterModel::filterExecutable() const
{
return m_filterExecutable;
}
void RulesFilterModel::setFilterExecutable(bool filterExecutable)
{
if (m_filterExecutable != filterExecutable) {
m_filterExecutable = filterExecutable;
emit filterExecutableChanged();
invalidateFilter();
emit countChanged();
}
}
Rule *RulesFilterModel::get(int index) const
{
return m_rules->get(mapToSource(this->index(index, 0)).row());
@ -53,9 +69,12 @@ Rule *RulesFilterModel::get(int index) const
bool RulesFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent)
Rule* rule = m_rules->get(source_row);
if (m_filterExecutable && !rule->executable()) {
return false;
}
bool found = true;
if (!m_filterDeviceId.isNull()) {
Rule* rule = m_rules->get(source_row);
found = false;
for (int i = 0; i < rule->eventDescriptors()->rowCount(); i++) {
EventDescriptor *ed = rule->eventDescriptors()->get(i);

View File

@ -13,6 +13,7 @@ class RulesFilterModel : public QSortFilterProxyModel
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
Q_PROPERTY(Rules* rules READ rules WRITE setRules NOTIFY rulesChanged)
Q_PROPERTY(QString filterDeviceId READ filterDeviceId WRITE setFilterDeviceId NOTIFY filterDeviceIdChanged)
Q_PROPERTY(bool filterExecutable READ filterExecutable WRITE setFilterExecutable NOTIFY filterExecutableChanged)
public:
explicit RulesFilterModel(QObject *parent = nullptr);
@ -23,11 +24,15 @@ public:
QString filterDeviceId() const;
void setFilterDeviceId(const QString &filterDeviceId);
bool filterExecutable() const;
void setFilterExecutable(bool filterExecutable);
Q_INVOKABLE Rule* get(int index) const;
signals:
void rulesChanged();
void filterDeviceIdChanged();
void filterExecutableChanged();
void countChanged();
protected:
@ -36,6 +41,7 @@ protected:
private:
Rules *m_rules = nullptr;
QString m_filterDeviceId;
bool m_filterExecutable = false;
};
#endif // RULESFILTERMODEL_H

View File

@ -0,0 +1,111 @@
#include "tagsproxymodel.h"
#include "engine.h"
#include "tagsmanager.h"
#include "types/tag.h"
TagsProxyModel::TagsProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
{
setSourceModel(Engine::instance()->tagsManager()->tags());
connect(Engine::instance()->tagsManager()->tags(), &Tags::countChanged, this, &TagsProxyModel::countChanged, Qt::QueuedConnection);
setSortRole(Tags::RoleValue);
sort(0);
}
QString TagsProxyModel::filterTagId() const
{
return m_filterTagId;
}
void TagsProxyModel::setFilterTagId(const QString &filterTagId)
{
if (m_filterTagId != filterTagId) {
m_filterTagId = filterTagId;
emit filterTagIdChanged();
invalidateFilter();
emit countChanged();
}
}
QString TagsProxyModel::filterDeviceId() const
{
return m_filterDeviceId;
}
void TagsProxyModel::setFilterDeviceId(const QString &filterDeviceId)
{
if (m_filterDeviceId != filterDeviceId) {
m_filterDeviceId = filterDeviceId;
emit filterDeviceIdChanged();
invalidateFilter();
emit countChanged();
}
}
QString TagsProxyModel::filterRuleId() const
{
return m_filterRuleId;
}
void TagsProxyModel::setFilterRuleId(const QString &filterRuleId)
{
if (m_filterRuleId != filterRuleId) {
m_filterRuleId = filterRuleId;
emit filterRuleIdChanged();
invalidateFilter();
emit countChanged();
}
}
Tag *TagsProxyModel::get(int index) const
{
if (index < 0 || index > rowCount()) {
return nullptr;
}
return Engine::instance()->tagsManager()->tags()->get(mapToSource(this->index(index, 0)).row());
}
Tag *TagsProxyModel::findTag(const QString &tagId) const
{
for (int i = 0; i < rowCount(); i++) {
Tag *tag = Engine::instance()->tagsManager()->tags()->get(mapToSource(index(i, 0)).row());
if (tag->tagId() == tagId) {
return tag;
}
}
return nullptr;
}
bool TagsProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent)
Tag *tag = Engine::instance()->tagsManager()->tags()->get(source_row);
if (!m_filterTagId.isEmpty()) {
if (tag->tagId() != m_filterTagId) {
return false;
}
}
if (!m_filterDeviceId.isEmpty()) {
if (tag->deviceId() != m_filterDeviceId) {
return false;
}
}
if (!m_filterRuleId.isEmpty()) {
if (tag->ruleId() != m_filterRuleId) {
return false;
}
}
return true;
}
bool TagsProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
QString leftValue = Engine::instance()->tagsManager()->tags()->get(source_left.row())->value();
QString rightValue = Engine::instance()->tagsManager()->tags()->get(source_right.row())->value();
bool okLeft, okRight;;
qlonglong leftAsNumber = leftValue.toLongLong(&okLeft);
qlonglong rightAsNumber = rightValue.toLongLong(&okRight);
if (okLeft && okRight) {
return leftAsNumber < rightAsNumber;
}
return leftValue < rightValue;
}

View File

@ -0,0 +1,47 @@
#ifndef TAGSPROXYMODEL_H
#define TAGSPROXYMODEL_H
#include <QSortFilterProxyModel>
class Tag;
class TagsProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
Q_PROPERTY(QString filterTagId READ filterTagId WRITE setFilterTagId NOTIFY filterTagIdChanged)
Q_PROPERTY(QString filterDeviceId READ filterDeviceId WRITE setFilterDeviceId NOTIFY filterDeviceIdChanged)
Q_PROPERTY(QString filterRuleId READ filterRuleId WRITE setFilterRuleId NOTIFY filterRuleIdChanged)
public:
explicit TagsProxyModel(QObject *parent = nullptr);
QString filterTagId() const;
void setFilterTagId(const QString &filterTagId);
QString filterDeviceId() const;
void setFilterDeviceId(const QString &filterDeviceId);
QString filterRuleId() const;
void setFilterRuleId(const QString &filterRuleId);
Q_INVOKABLE Tag* get(int index) const;
Q_INVOKABLE Tag* findTag(const QString &tagId) const;
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
signals:
void filterTagIdChanged();
void filterDeviceIdChanged();
void filterRuleIdChanged();
void countChanged();
private:
QString m_filterTagId;
QString m_filterDeviceId;
QString m_filterRuleId;
};
#endif // TAGSPROXYMODEL_H

View File

@ -4,6 +4,7 @@
#include <QDebug>
#include <QSslKey>
#include <QSettings>
#include <QMetaEnum>
#include "nymeainterface.h"
#include "tcpsocketinterface.h"
@ -133,8 +134,8 @@ void NymeaConnection::onSslErrors(const QList<QSslError> &errors)
void NymeaConnection::onError(QAbstractSocket::SocketError error)
{
qWarning() << "Socket error" << error;
emit connectionError();
QMetaEnum errorEnum = QMetaEnum::fromType<QAbstractSocket::SocketError>();
emit connectionError(errorEnum.valueToKey(error));
}
void NymeaConnection::onConnected()

View File

@ -34,7 +34,7 @@ public:
signals:
void verifyConnectionCertificate(const QString &url, const QStringList &issuerInfo, const QByteArray &fingerprint);
void connectedChanged(bool connected);
void connectionError();
void connectionError(const QString &error);
void dataAvailable(const QByteArray &data);
private slots:

View File

@ -80,9 +80,16 @@ void RuleManager::editRule(Rule *rule)
}
void RuleManager::executeActions(const QString &ruleId)
{
QVariantMap params;
params.insert("ruleId", ruleId);
m_jsonClient->sendCommand("Rules.ExecuteActions", params, this, "onExecuteRuleActionsReply");
}
void RuleManager::handleRulesNotification(const QVariantMap &params)
{
// qDebug() << "rules notification received" << params;
qDebug() << "rules notification received" << params;
if (params.value("notification").toString() == "Rules.RuleAdded") {
QVariantMap ruleMap = params.value("params").toMap().value("rule").toMap();
m_rules->insert(parseRule(ruleMap));
@ -124,11 +131,13 @@ void RuleManager::getRulesReply(const QVariantMap &params)
QString name = ruleDescriptionVariant.toMap().value("name").toString();
bool enabled = ruleDescriptionVariant.toMap().value("enabled").toBool();
bool active = ruleDescriptionVariant.toMap().value("active").toBool();
bool executable = ruleDescriptionVariant.toMap().value("executable").toBool();
Rule *rule = new Rule(ruleId, m_rules);
rule->setName(name);
rule->setEnabled(enabled);
rule->setActive(active);
rule->setExecutable(executable);
m_rules->insert(rule);
QVariantMap requestParams;
@ -156,7 +165,7 @@ void RuleManager::getRuleDetailsReply(const QVariantMap &params)
void RuleManager::onAddRuleReply(const QVariantMap &params)
{
qDebug() << "Add rule reply" << params;
emit addRuleReply(params.value("params").toMap().value("ruleError").toString());
emit addRuleReply(params.value("params").toMap().value("ruleError").toString(), params.value("params").toMap().value("ruleId").toString());
}
void RuleManager::removeRuleReply(const QVariantMap &params)
@ -171,16 +180,23 @@ void RuleManager::onEditRuleReply(const QVariantMap &params)
emit editRuleReply(params.value("params").toMap().value("ruleError").toString());
}
void RuleManager::onExecuteRuleActionsReply(const QVariantMap &params)
{
qDebug() << "Execute rule actions reply:" << params;
}
Rule *RuleManager::parseRule(const QVariantMap &ruleMap)
{
QUuid ruleId = ruleMap.value("id").toUuid();
QString name = ruleMap.value("name").toString();
bool enabled = ruleMap.value("enabled").toBool();
bool active = ruleMap.value("active").toBool();
bool executable = ruleMap.value("executable").toBool();
Rule* rule = new Rule(ruleId);
rule->setName(name);
rule->setEnabled(enabled);
rule->setActive(active);
rule->setExecutable(executable);
parseEventDescriptors(ruleMap.value("eventDescriptors").toList(), rule);
parseRuleActions(ruleMap.value("actions").toList(), rule);
parseRuleExitActions(ruleMap.value("exitActions").toList(), rule);

View File

@ -31,6 +31,7 @@ public:
Q_INVOKABLE void addRule(Rule *rule);
Q_INVOKABLE void removeRule(const QUuid &ruleId);
Q_INVOKABLE void editRule(Rule *rule);
Q_INVOKABLE void executeActions(const QString &ruleId);
private slots:
void handleRulesNotification(const QVariantMap &params);
@ -39,6 +40,7 @@ private slots:
void onAddRuleReply(const QVariantMap &params);
void removeRuleReply(const QVariantMap &params);
void onEditRuleReply(const QVariantMap &params);
void onExecuteRuleActionsReply(const QVariantMap &params);
private:
Rule *parseRule(const QVariantMap &ruleMap);
@ -50,7 +52,7 @@ private:
void parseTimeDescriptor(const QVariantMap &timeDescriptor, Rule *rule);
signals:
void addRuleReply(const QString &ruleError);
void addRuleReply(const QString &ruleError, const QString &ruleId);
void editRuleReply(const QString &ruleError);
private:

View File

@ -0,0 +1,151 @@
#include "tagsmanager.h"
#include "types/tag.h"
#include "engine.h"
TagsManager::TagsManager(JsonRpcClient *jsonClient, QObject *parent):
JsonHandler(parent),
m_jsonClient(jsonClient),
m_tags(new Tags(this))
{
jsonClient->registerNotificationHandler(this, "handleTagsNotification");
}
QString TagsManager::nameSpace() const
{
return "Tags";
}
void TagsManager::init()
{
m_tags->clear();
m_jsonClient->sendCommand("Tags.GetTags", this, "getTagsReply");
}
Tags *TagsManager::tags() const
{
return m_tags;
}
void TagsManager::tagDevice(const QString &deviceId, const QString &tagId, const QString &value)
{
QVariantMap params;
QVariantMap tag;
tag.insert("deviceId", deviceId);
tag.insert("appId", "nymea:app");
tag.insert("tagId", tagId);
tag.insert("value", value);
params.insert("tag", tag);
m_jsonClient->sendCommand("Tags.AddTag", params, this, "addTagReply");
}
void TagsManager::untagDevice(const QString &deviceId, const QString &tagId)
{
QVariantMap params;
QVariantMap tag;
tag.insert("deviceId", deviceId);
tag.insert("appId", "nymea:app");
tag.insert("tagId", tagId);
params.insert("tag", tag);
m_jsonClient->sendCommand("Tags.RemoveTag", params, this, "removeTagReply");
}
void TagsManager::tagRule(const QString &ruleId, const QString &tagId, const QString &value)
{
QVariantMap params;
QVariantMap tag;
tag.insert("ruleId", ruleId);
tag.insert("appId", "nymea:app");
tag.insert("tagId", tagId);
tag.insert("value", value);
params.insert("tag", tag);
m_jsonClient->sendCommand("Tags.AddTag", params, this, "addTagReply");
}
void TagsManager::untagRule(const QString &ruleId, const QString &tagId)
{
QVariantMap params;
QVariantMap tag;
tag.insert("ruleId", ruleId);
tag.insert("appId", "nymea:app");
tag.insert("tagId", tagId);
params.insert("tag", tag);
m_jsonClient->sendCommand("Tags.RemoveTag", params, this, "removeTagReply");
}
void TagsManager::handleTagsNotification(const QVariantMap &params)
{
qDebug() << "Have tags notification" << params;
QVariantMap tagMap = params.value("params").toMap().value("tag").toMap();
if (tagMap.value("appId").toString() != "nymea:app") {
return; // not for us
}
QString notification = params.value("notification").toString();
if (notification == "Tags.TagAdded") {
addTagInternal(tagMap);
} else if (notification == "Tags.TagRemoved") {
for (int i = 0; i < m_tags->rowCount(); i++) {
Tag* tag = m_tags->get(i);
if (tagMap.value("deviceId").toString() == tag->deviceId() &&
tagMap.value("ruleId").toString() == tag->ruleId() &&
tagMap.value("tagId").toString() == tag->tagId()) {
m_tags->removeTag(tag);
return;
}
}
} else if (notification == "Tags.TagValueChanged") {
qDebug() << "tag value changed";
for (int i = 0; i < m_tags->rowCount(); i++) {
Tag* tag = m_tags->get(i);
if (tagMap.value("deviceId").toString() == tag->deviceId() &&
tagMap.value("ruleId").toString() == tag->ruleId() &&
tagMap.value("tagId").toString() == tag->tagId()) {
qDebug() << "Found tag";
tag->setValue(tagMap.value("value").toString());
}
}
}
}
void TagsManager::getTagsReply(const QVariantMap &params)
{
qDebug() << "Have tags" << params;
foreach (const QVariant &tagVariant, params.value("params").toMap().value("tags").toList()) {
addTagInternal(tagVariant.toMap());
}
emit tagsChanged();
}
void TagsManager::addTagReply(const QVariantMap &params)
{
qDebug() << "AddTag reply" << params;
}
void TagsManager::removeTagReply(const QVariantMap &params)
{
qDebug() << "RemoveTag reply" << params;
}
void TagsManager::addTagInternal(const QVariantMap &tagMap)
{
QString deviceId = tagMap.value("deviceId").toString();
QString ruleId = tagMap.value("ruleId").toString();
QString tagId = tagMap.value("tagId").toString();
QString value = tagMap.value("value").toString();
Tag *tag = nullptr;
if (!deviceId.isEmpty()) {
tag = new Tag(tagId, value);
tag->setDeviceId(deviceId);
} else if (!ruleId.isEmpty()) {
tag = new Tag(tagId, value);
tag->setRuleId(ruleId);
} else {
qWarning() << "Invalid tag. Neither deviceId nor ruleId are set. Skipping...";
tag->deleteLater();
return;
}
qDebug() << "adding tag" << tag->tagId() << tag->value();
m_tags->addTag(tag);
}

View File

@ -0,0 +1,44 @@
#ifndef TAGSMANAGER_H
#define TAGSMANAGER_H
#include "jsonrpc/jsonhandler.h"
#include "jsonrpc/jsonrpcclient.h"
#include "types/tags.h"
class TagsManager : public JsonHandler
{
Q_OBJECT
Q_PROPERTY(Tags* tags READ tags NOTIFY tagsChanged)
public:
explicit TagsManager(JsonRpcClient *jsonClient, QObject *parent = nullptr);
QString nameSpace() const override;
void init();
Tags* tags() const;
Q_INVOKABLE void tagDevice(const QString &deviceId, const QString &tagId, const QString &value);
Q_INVOKABLE void untagDevice(const QString &deviceId, const QString &tagId);
Q_INVOKABLE void tagRule(const QString &ruleId, const QString &tagId, const QString &value);
Q_INVOKABLE void untagRule(const QString &ruleId, const QString &tagId);
signals:
void tagsChanged();
private slots:
void handleTagsNotification(const QVariantMap &params);
void getTagsReply(const QVariantMap &params);
void addTagReply(const QVariantMap &params);
void removeTagReply(const QVariantMap &params);
private:
void addTagInternal(const QVariantMap &tagMap);
JsonRpcClient *m_jsonClient = nullptr;
Tags *m_tags = nullptr;
};
#endif // TAGSMANAGER_H

View File

@ -52,7 +52,9 @@ HEADERS += \
types/calendaritem.h \
types/timeeventitems.h \
types/calendaritems.h \
types/repeatingoption.h
types/repeatingoption.h \
types/tag.h \
types/tags.h
SOURCES += \
types/vendor.cpp \
@ -95,7 +97,9 @@ SOURCES += \
types/calendaritem.cpp \
types/timeeventitems.cpp \
types/calendaritems.cpp \
types/repeatingoption.cpp
types/repeatingoption.cpp \
types/tag.cpp \
types/tags.cpp
# install header file with relative subdirectory
for(header, HEADERS) {

View File

@ -24,7 +24,7 @@ Rule::Rule(const QUuid &id, QObject *parent) :
m_exitActions(new RuleActions(this)),
m_timeDescriptor(new TimeDescriptor(this))
{
qDebug() << "### Creating rule" << this;
// qDebug() << "### Creating rule" << this;
}
Rule::~Rule()
@ -76,6 +76,19 @@ void Rule::setActive(bool active)
}
}
bool Rule::executable() const
{
return m_executable;
}
void Rule::setExecutable(bool executable)
{
if (m_executable != executable) {
m_executable = executable;
emit executableChanged();
}
}
EventDescriptors *Rule::eventDescriptors() const
{
return m_eventDescriptors;
@ -123,6 +136,7 @@ Rule *Rule::clone() const
Rule *ret = new Rule(this->id());
ret->setName(this->name());
ret->setEnabled(this->enabled());
ret->setExecutable(this->executable());
for (int i = 0; i < this->eventDescriptors()->rowCount(); i++) {
ret->eventDescriptors()->addEventDescriptor(this->eventDescriptors()->get(i)->clone());
}

View File

@ -16,6 +16,7 @@ class Rule : public QObject
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
Q_PROPERTY(bool active READ active NOTIFY activeChanged)
Q_PROPERTY(bool executable READ executable WRITE setExecutable NOTIFY executableChanged)
Q_PROPERTY(EventDescriptors* eventDescriptors READ eventDescriptors CONSTANT)
Q_PROPERTY(StateEvaluator* stateEvaluator READ stateEvaluator WRITE setStateEvaluator NOTIFY stateEvaluatorChanged)
Q_PROPERTY(RuleActions* actions READ actions CONSTANT)
@ -36,6 +37,9 @@ public:
bool active() const;
void setActive(bool active);
bool executable() const;
void setExecutable(bool executable);
EventDescriptors* eventDescriptors() const;
StateEvaluator *stateEvaluator() const;
RuleActions* actions() const;
@ -52,6 +56,7 @@ signals:
void nameChanged();
void enabledChanged();
void activeChanged();
void executableChanged();
void stateEvaluatorChanged();
private:
@ -59,6 +64,7 @@ private:
QString m_name;
bool m_enabled = true;
bool m_active = false;
bool m_executable = false;
EventDescriptors *m_eventDescriptors = nullptr;
StateEvaluator *m_stateEvaluator = nullptr;
RuleActions *m_actions = nullptr;

View File

@ -34,6 +34,8 @@ QVariant Rules::data(const QModelIndex &index, int role) const
return m_list.at(index.row())->enabled();
case RoleActive:
return m_list.at(index.row())->active();
case RoleExecutable:
return m_list.at(index.row())->executable();
}
return QVariant();
}
@ -45,6 +47,7 @@ QHash<int, QByteArray> Rules::roleNames() const
roles.insert(RoleId, "id");
roles.insert(RoleEnabled, "enabled");
roles.insert(RoleActive, "active");
roles.insert(RoleExecutable, "executable");
return roles;
}
@ -56,6 +59,7 @@ void Rules::insert(Rule *rule)
connect(rule, &Rule::enabledChanged, this, &Rules::ruleChanged);
connect(rule, &Rule::activeChanged, this, &Rules::ruleChanged);
connect(rule, &Rule::nameChanged, this, &Rules::ruleChanged);
connect(rule, &Rule::executableChanged, this, &Rules::ruleChanged);
endInsertRows();
emit countChanged();
}
@ -103,5 +107,5 @@ void Rules::ruleChanged()
return;
}
QModelIndex modelIndex = index(idx);
emit dataChanged(modelIndex, modelIndex, {RoleActive, RoleEnabled, RoleName});
emit dataChanged(modelIndex, modelIndex, {RoleActive, RoleEnabled, RoleName, RoleExecutable});
}

View File

@ -14,7 +14,8 @@ public:
RoleName,
RoleId,
RoleEnabled,
RoleActive
RoleActive,
RoleExecutable
};
explicit Rules(QObject *parent = nullptr);

View File

@ -0,0 +1,50 @@
#include "tag.h"
#include <QDebug>
Tag::Tag(const QString &tagId, const QString &value, QObject *parent):
QObject(parent),
m_tagId(tagId),
m_value(value)
{
}
QString Tag::deviceId() const
{
return m_deviceId;
}
void Tag::setDeviceId(const QString &deviceId)
{
m_deviceId = deviceId;
}
QString Tag::ruleId() const
{
return m_ruleId;
}
void Tag::setRuleId(const QString &ruleId)
{
m_ruleId = ruleId;
}
QString Tag::tagId() const
{
return m_tagId;
}
QString Tag::value() const
{
return m_value;
}
void Tag::setValue(const QString &value)
{
if (m_value != value) {
m_value = value;
qDebug() << "tags value changed" << m_deviceId << m_tagId << value;
emit valueChanged();
}
}

View File

@ -0,0 +1,38 @@
#ifndef TAG_H
#define TAG_H
#include <QObject>
class Tag : public QObject
{
Q_OBJECT
Q_PROPERTY(QString deviceId READ deviceId CONSTANT)
Q_PROPERTY(QString ruleId READ ruleId CONSTANT)
Q_PROPERTY(QString tagId READ tagId CONSTANT)
Q_PROPERTY(QString value READ value NOTIFY valueChanged)
public:
explicit Tag(const QString &tagId, const QString &value, QObject *parent = nullptr);
QString deviceId() const;
void setDeviceId(const QString &deviceId);
QString ruleId() const;
void setRuleId(const QString &ruleId);
QString tagId() const;
QString value() const;
void setValue(const QString &value);
signals:
void valueChanged();
private:
QString m_deviceId;
QString m_ruleId;
QString m_tagId;
QString m_value;
};
#endif // TAG_H

View File

@ -0,0 +1,106 @@
#include "tags.h"
#include "tag.h"
#include <QDebug>
Tags::Tags(QObject *parent) : QAbstractListModel(parent)
{
}
int Tags::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_list.count();
}
QVariant Tags::data(const QModelIndex &index, int role) const
{
switch (role) {
case RoleDeviceId:
return m_list.at(index.row())->deviceId();
case RoleRuleId:
return m_list.at(index.row())->ruleId();
case RoleTagId:
return m_list.at(index.row())->tagId();
case RoleValue:
return m_list.at(index.row())->value();
}
return QVariant();
}
QHash<int, QByteArray> Tags::roleNames() const
{
QHash<int, QByteArray> roles;
roles.insert(RoleDeviceId, "deviceId");
roles.insert(RoleRuleId, "ruleId");
roles.insert(RoleTagId, "tagId");
roles.insert(RoleValue, "value");
return roles;
}
void Tags::addTag(Tag *tag)
{
tag->setParent(this);
connect(tag, &Tag::valueChanged, this, &Tags::tagValueChanged);
beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
m_list.append(tag);
endInsertRows();
emit countChanged();
}
void Tags::removeTag(Tag *tag)
{
int idx = m_list.indexOf(tag);
if (idx < 0) {
qWarning() << "Don't know this tag. Can't remove";
return;
}
beginRemoveRows(QModelIndex(), idx, idx);
m_list.removeAt(idx);
endRemoveRows();
tag->deleteLater();
emit countChanged();
}
Tag *Tags::get(int index) const
{
return m_list.at(index);
}
Tag *Tags::findDeviceTag(const QString &deviceId, const QString &tagId) const
{
foreach (Tag *tag, m_list) {
if (tag->deviceId() == deviceId && tag->tagId() == tagId) {
return tag;
}
}
return nullptr;
}
Tag *Tags::findRuleTag(const QString &ruleId, const QString &tagId) const
{
foreach (Tag *tag, m_list) {
if (tag->ruleId() == ruleId && tag->tagId() == tagId) {
return tag;
}
}
return nullptr;
}
void Tags::clear()
{
beginResetModel();
qDeleteAll(m_list);
m_list.clear();
endResetModel();
emit countChanged();
}
void Tags::tagValueChanged()
{
qDebug() << "Tag value in mode changed";
Tag *tag = static_cast<Tag*>(sender());
int idx = m_list.indexOf(tag);
emit dataChanged(index(idx, 0), index(idx, 0), {RoleValue});
}

View File

@ -0,0 +1,47 @@
#ifndef TAGS_H
#define TAGS_H
#include <QAbstractListModel>
class Tag;
class Tags: public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
enum Roles {
RoleDeviceId,
RoleRuleId,
RoleTagId,
RoleValue
};
Q_ENUM(Roles)
explicit Tags(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
void addTag(Tag *tag);
void removeTag(Tag *tag);
Tag* get(int index) const;
Q_INVOKABLE Tag* findDeviceTag(const QString &deviceId, const QString &tagId) const;
Q_INVOKABLE Tag* findRuleTag(const QString &ruleId, const QString &tagId) const;
void clear();
signals:
void countChanged();
private slots:
void tagValueChanged();
private:
QList<Tag*> m_list;
};
#endif // TAGS_H

View File

@ -6,6 +6,8 @@ SUBDIRS = libnymea-common libnymea-app-core nymea-app
libnymea-app-core.depends = libnymea-common
nymea-app.depends = libnymea-app-core
#QML_IMPORT_PATH=/home/micha/Develop/Qt/5.11.0/gcc_64/qml/
withtests: {
SUBDIRS += tests
tests.depends = libnymea-app-core

View File

@ -2,7 +2,7 @@ TEMPLATE=app
TARGET=nymea-app
include(../config.pri)
QT += qml quick quickcontrols2 svg websockets bluetooth
QT += network qml quick quickcontrols2 svg websockets bluetooth
INCLUDEPATH += $$top_srcdir/libnymea-common \
$$top_srcdir/libnymea-app-core

View File

@ -2,7 +2,7 @@
<qresource prefix="/">
<file>ui/Nymea.qml</file>
<file>ui/ConnectPage.qml</file>
<file>ui/DevicesPage.qml</file>
<file>ui/mainviews/DevicesPage.qml</file>
<file>ui/NewDeviceWizard.qml</file>
<file>ui/SettingsPage.qml</file>
<file>ui/components/GuhHeader.qml</file>
@ -203,5 +203,14 @@
<file>ui/images/rpi-setup.svg</file>
<file>ui/images/eye.svg</file>
<file>ui/images/private-browsing.svg</file>
<file>ui/images/starred.svg</file>
<file>ui/images/non-starred.svg</file>
<file>ui/images/slideshow.svg</file>
<file>ui/components/MainPageTabButton.qml</file>
<file>ui/mainviews/ScenesView.qml</file>
<file>ui/mainviews/FavoritesView.qml</file>
<file>ui/mainviews/DevicesPageDelegate.qml</file>
<file>ui/components/AutoSizeMenu.qml</file>
<file>ui/components/EmptyViewPlaceholder.qml</file>
</qresource>
</RCC>

View File

@ -28,6 +28,20 @@ Page {
popup.open();
}
onConnectionError: {
var errorMessage;
switch (error) {
case "ConnectionRefusedError":
errorMessage = qsTr("The host has rejected our connection. This probably means that %1 stopped running. Did you unplug your %1 box?").arg(app.systemName);
break;
case "SslInvalidUserDataError":
case "SslHandshakeFailedError":
// silently ignore. They'll be handled by the SSL logic
return;
}
var comp = Qt.createComponent(Qt.resolvedUrl("components/ErrorDialog.qml"))
var popup = comp.createObject(app, {text: errorMessage})
popup.open()
pageStack.pop(root)
pageStack.push(discoveryPage)
}

View File

@ -1,284 +0,0 @@
import QtQuick 2.8
import QtQuick.Controls 2.1
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.2
import Nymea 1.0
import "components"
Item {
id: root
property alias count: interfacesGridView.count
property alias model: interfacesGridView.model
GridView {
id: interfacesGridView
anchors.fill: parent
anchors.margins: app.margins / 2
readonly property int minTileWidth: 180
readonly property int minTileHeight: 240
readonly property int tilesPerRow: root.width / minTileWidth
model: InterfacesModel {
id: interfacesModel
devices: Engine.deviceManager.devices
}
cellWidth: width / tilesPerRow
cellHeight: Math.max(cellWidth, minTileHeight)
delegate: Item {
width: interfacesGridView.cellWidth
height: interfacesGridView.cellHeight
Pane {
anchors.fill: parent
anchors.margins: app.margins / 2
Material.elevation: 1
Column {
width: parent.width
anchors.centerIn: parent
anchors.verticalCenterOffset: -app.iconSize
spacing: app.margins
ColorIcon {
height: app.iconSize * 2
width: height
color: app.guhAccent
anchors.horizontalCenter: parent.horizontalCenter
name: interfaceToIcon(model.name)
}
Label {
text: interfaceToString(model.name).toUpperCase()
width: parent.width
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
}
MouseArea {
anchors.fill: parent
onClicked: {
var page;
switch (model.name) {
case "light":
page = "LightsDeviceListPage.qml"
break;
default:
page = "GenericDeviceListPage.qml"
}
pageStack.push(Qt.resolvedUrl("devicelistpages/" + page), {filterInterface: model.name})
}
}
DevicesProxy {
id: devicesProxy
devices: Engine.deviceManager.devices
filterInterface: model.name
}
}
Item {
id: inlineControlPane
anchors { left: parent.left; bottom: parent.bottom; right: parent.right; margins: app.margins / 2 }
height: app.iconSize + app.margins * 2
Rectangle {
anchors.fill: parent
// color: app.guhAccent
color: "black"
opacity: .05
}
Loader {
id: inlineControlLoader
anchors {
fill: parent
leftMargin: app.margins
rightMargin: app.margins
topMargin: app.margins / 2
bottomMargin: app.margins / 2
}
sourceComponent: {
switch (model.name) {
case "sensor":
case "weather":
return labelComponent;
case "light":
case "media":
case "garagegate":
case "shutter":
case "blind":
return buttonComponent
}
}
}
}
Component {
id: buttonComponent
MouseArea {
onClicked: {
switch (model.name) {
case "light":
if (devicesProxy.count == 1) {
var device = devicesProxy.get(0);
var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
var stateType = deviceClass.stateTypes.findByName("power")
var actionType = deviceClass.actionTypes.findByName("power")
var params = [];
var param1 = {};
param1["paramTypeId"] = actionType.paramTypes.get(0).id;
param1["value"] = !device.states.getState(stateType.id).value;
params.push(param1)
Engine.deviceManager.executeAction(device.id, actionType.id, params)
} else {
for (var i = 0; i < devicesProxy.count; i++) {
var device = devicesProxy.get(i);
var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
var actionType = deviceClass.actionTypes.findByName("power");
var params = [];
var param1 = {};
param1["paramTypeId"] = actionType.paramTypes.get(0).id;
param1["value"] = false;
params.push(param1)
Engine.deviceManager.executeAction(device.id, actionType.id, params)
}
}
break;
case "media":
var device = devicesProxy.get(0)
var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
var stateType = deviceClass.stateTypes.findByName("playbackStatus");
var state = device.states.getState(stateType.id)
var actionName
switch (state.value) {
case "PLAYING":
actionName = "pause";
break;
case "PAUSED":
actionName = "play";
break;
}
var actionTypeId = deviceClass.actionTypes.findByName(actionName).id;
print("executing", device, device.id, actionTypeId, actionName, deviceClass.actionTypes)
Engine.deviceManager.executeAction(device.id, actionTypeId)
case "garagegate":
case "shutter":
case "blind":
for (var i = 0; i < devicesProxy.count; i++) {
var device = devicesProxy.get(i);
var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
var actionType = deviceClass.actionTypes.findByName("close");
Engine.deviceManager.executeAction(device.id, actionType.id)
}
}
}
ColumnLayout {
anchors.fill: parent
Label {
id: label
Layout.fillWidth: true
text: {
switch (model.name) {
case "media":
return devicesProxy.get(0).name;
case "light":
var count = 0;
for (var i = 0; i < devicesProxy.count; i++) {
var device = devicesProxy.get(i);
var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
var stateType = deviceClass.stateTypes.findByName("power")
if (device.states.getState(stateType.id).value === true) {
count++;
}
}
return count === 0 ? qsTr("All off") : qsTr("%1 on").arg(count)
case "garagegate":
var count = 0;
for (var i = 0; i < devicesProxy.count; i++) {
var device = devicesProxy.get(i);
var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
var stateType = deviceClass.stateTypes.findByName("state");
if (device.states.getState(stateType.id).value !== "closed") {
count++;
}
}
return count === 0 ? qsTr("All closed") : qsTr("%1 open").arg(count)
case "shutter":
return qsTr("%1 installed").arg(devicesProxy.count)
}
console.warn("Unhandled interface", model.name)
}
font.pixelSize: app.smallFont
elide: Text.ElideRight
}
ColorIcon {
id: icon
width: app.largeFont
height: width
color: app.guhAccent
anchors.right: parent.right
name: {
switch (model.name) {
case "media":
var device = devicesProxy.get(0)
var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
var stateType = deviceClass.stateTypes.findByName("playbackStatus");
var state = device.states.getState(stateType.id)
return state.value === "PLAYING" ? "../images/media-playback-pause.svg" :
state.value === "PAUSED" ? "../images/media-playback-start.svg" :
""
case "light":
return "../images/system-shutdown.svg"
case "garagegate":
case "shutter":
case "blind":
return "../images/down.svg"
}
}
}
}
}
}
Component {
id: labelComponent
ColumnLayout {
property var device: devicesProxy.get(0)
property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null
property var state: deviceClass ? device.states.getState(deviceClass.stateTypes.findByName("temperature").id) : null
Label {
text: parent.device.name
font.pixelSize: app.smallFont
Layout.fillWidth: true
elide: Text.ElideRight
}
Label {
font.pixelSize: app.largeFont
color: app.guhAccent
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
text: {
if (devicesProxy.count > 0) {
var stateName;
// switch (model.name) {
// case "sensor":
// }
return parent.state.value + "°C";
}
}
}
}
}
}
}
}

View File

@ -33,42 +33,14 @@ Page {
}
}
ColumnLayout {
EmptyViewPlaceholder {
anchors { left: parent.left; right: parent.right; margins: app.margins }
anchors.verticalCenter: parent.verticalCenter
spacing: app.margins * 2
visible: Engine.deviceManager.devices.count === 0 && !Engine.deviceManager.fetchingData
Label {
text: qsTr("There are no things set up yet.")
font.pixelSize: app.largeFont
Layout.fillWidth: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
color: app.guhAccent
}
Label {
text: qsTr("In order for your %1 box to be useful, go ahead and add some things.").arg(app.systemName)
Layout.fillWidth: true
Layout.maximumWidth: 400
Layout.alignment: Qt.AlignHCenter
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
Image {
source: "qrc:/styles/%1/logo.svg".arg(styleController.currentStyle)
Layout.preferredWidth: app.iconSize * 5
Layout.preferredHeight: width
Layout.alignment: Qt.AlignHCenter
sourceSize.width: app.iconSize * 5
sourceSize.height: app.iconSize * 5
}
Button {
Layout.fillWidth: true
Layout.maximumWidth: 400
Layout.alignment: Qt.AlignHCenter
text: qsTr("Add a thing")
onClicked: pageStack.push(Qt.resolvedUrl("NewDeviceWizard.qml"))
}
title: qsTr("There are no things set up yet.")
text: qsTr("In order for your %1 box to be useful, go ahead and add some things.").arg(app.systemName)
imageSource: "qrc:/styles/%1/logo.svg".arg(styleController.currentStyle)
buttonText: qsTr("Add a thing")
onButtonClicked: pageStack.push(Qt.resolvedUrl("NewDeviceWizard.qml"))
}
}

View File

@ -43,6 +43,9 @@ Page {
onAddRuleReply: {
d.editRulePage.busy = false;
if (ruleError == "RuleErrorNoError") {
print("should tag rule now:", d.editRulePage.rule.id, d.editRulePage.ruleIcon, d.editRulePage.ruleColor)
Engine.tagsManager.tagRule(ruleId, "color", d.editRulePage.ruleColor)
Engine.tagsManager.tagRule(ruleId, "icon", d.editRulePage.ruleIcon)
pageStack.pop();
} else {
var popup = errorDialog.createObject(app, {errorCode: ruleError })
@ -53,6 +56,9 @@ Page {
onEditRuleReply: {
d.editRulePage.busy = false;
if (ruleError == "RuleErrorNoError") {
print("should tag rule now:", d.editRulePage.ruleIcon, d.editRulePage.ruleColor)
Engine.tagsManager.tagRule(d.editRulePage.rule.id, "color", d.editRulePage.ruleColor)
Engine.tagsManager.tagRule(d.editRulePage.rule.id, "icon", d.editRulePage.ruleIcon)
pageStack.pop();
} else {
var popup = errorDialog.createObject(app, {errorCode: ruleError })
@ -64,19 +70,32 @@ Page {
ListView {
anchors.fill: parent
model: Engine.ruleManager.rules
model: RulesFilterModel {
id: rulesProxy
rules: Engine.ruleManager.rules
}
delegate: MeaListItemDelegate {
id: ruleDelegate
width: parent.width
iconName: "../images/magic.svg"
iconColor: !model.enabled ? "red" : (model.active ? app.guhAccent : "grey")
iconName: "../images/" + (model.executable ? (iconTag ? iconTag.value : "slideshow") : "magic") + ".svg"
iconColor: model.executable ? (colorTag ? colorTag.value : app.guhAccent) : !model.enabled ? "red" : (model.active ? app.guhAccent : "grey")
text: model.name
canDelete: true
property var colorTag: model.executable ? Engine.tagsManager.tags.findRuleTag(model.id, "color") : null
property var iconTag: model.executable ? Engine.tagsManager.tags.findRuleTag(model.id, "icon") : null
Connections {
target: Engine.tagsManager.tags
onCountChanged: {
colorTag = Engine.tagsManager.tags.findRuleTag(model.id, "color")
iconTag = Engine.tagsManager.tags.findRuleTag(model.id, "icon")
}
}
onDeleteClicked: Engine.ruleManager.removeRule(model.id)
onClicked: {
var newRule = Engine.ruleManager.rules.get(index).clone();
var newRule = rulesProxy.get(index).clone();
d.editRulePage = pageStack.push(Qt.resolvedUrl("magic/EditRulePage.qml"), {rule: newRule})
d.editRulePage.StackView.onRemoved.connect(function() {
newRule.destroy();
@ -92,37 +111,16 @@ Page {
}
}
ColumnLayout {
EmptyViewPlaceholder {
anchors { left: parent.left; right: parent.right; margins: app.margins }
anchors.verticalCenter: parent.verticalCenter
spacing: app.margins * 2
visible: Engine.ruleManager.rules.count === 0
Label {
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
text: qsTr("There is no magic set up yet.")
wrapMode: Text.WordWrap
color: app.guhAccent
font.pixelSize: app.largeFont
}
Label {
text: qsTr("Add some using the wizard stick!")
Layout.fillWidth: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
AbstractButton {
Layout.preferredHeight: app.iconSize * 4
Layout.preferredWidth: height
Layout.alignment: Qt.AlignHCenter
ColorIcon {
anchors.fill: parent
name: "../images/magic.svg"
}
onClicked: addRule()
}
visible: Engine.ruleManager.rules.count === 0
title: qsTr("There is no magic set up yet.")
text: qsTr("Use magic to make your things smart! In a few easy steps you'll have your things wired up and work for you.")
imageSource: "images/magic.svg"
buttonText: qsTr("Add some magic")
onImageClicked: addRule()
onButtonClicked: addRule()
}
Component {

View File

@ -1,16 +1,17 @@
import QtQuick 2.8
import QtQuick.Controls 2.1
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.2
import Nymea 1.0
import "components"
import "delegates"
import "mainviews"
Page {
id: root
header: GuhHeader {
text: qsTr("My things")
text: swipeView.currentItem.title
backButtonVisible: false
menuButtonVisible: true
onMenuPressed: mainMenu.open()
@ -36,72 +37,134 @@ Page {
// }
// }
Menu {
AutoSizeMenu {
id: mainMenu
width: implicitWidth + app.margins
IconMenuItem {
iconSource: "../images/share.svg"
text: qsTr("Configure things")
width: parent.width
onTriggered: pageStack.push(Qt.resolvedUrl("EditDevicesPage.qml"))
}
// MenuSeparator {}
IconMenuItem {
iconSource: "../images/magic.svg"
text: qsTr("Magic")
width: parent.width
onTriggered: pageStack.push(Qt.resolvedUrl("MagicPage.qml"))
}
MenuSeparator {}
MenuSeparator { width: parent.width }
IconMenuItem {
iconSource: "../images/settings.svg"
text: qsTr("System settings")
width: parent.width
onTriggered: pageStack.push(Qt.resolvedUrl("SettingsPage.qml"))
}
MenuSeparator {}
MenuSeparator { width: parent.width }
IconMenuItem {
iconSource: "../images/stock_application.svg"
text: qsTr("App settings")
width: parent.width
onTriggered: pageStack.push(Qt.resolvedUrl("AppSettingsPage.qml"))
}
}
InterfacesModel {
id: page1Model
devices: Engine.deviceManager.devices
shownInterfaces: ["light", "weather", "sensor", "media", "garagegate", "shutter", "garagegate"]
property var view: null
onCountChanged: buildView()
}
InterfacesModel {
id: page2Model
devices: Engine.deviceManager.devices
shownInterfaces: ["gateway", "button", "notifications", "inputtrigger", "outputtrigger"]
property var view: null
onCountChanged: buildView()
}
ColumnLayout {
anchors.fill: parent
Component {
id: devicePageComponent
DevicesPage {
width: swipeView.width
height: swipeView.height
visible: count > 0
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Component {
id: allDevicesComponent
ListView {
width: swipeView.width
height: swipeView.height
model: DevicesProxy {
id: devicesProxy
devices: Engine.deviceManager.devices
}
delegate: ThingDelegate {
interfaces: model.interfaces
name: model.name
onClicked: {
pageStack.push(Qt.resolvedUrl("devicepages/GenericDevicePage.qml"), {device: devicesProxy.get(index)})
SwipeView {
id: swipeView
clip: true
anchors.fill: parent
currentIndex: settings.currentMainViewIndex
onCurrentIndexChanged: settings.currentMainViewIndex = currentIndex
opacity: Engine.deviceManager.fetchingData ? 0 : 1
Behavior on opacity { NumberAnimation { duration: 300 } }
Component.onCompleted: {
if (Engine.jsonRpcClient.ensureServerVersion(1.6)) {
swipeView.insertItem(0, favoritesViewComponent.createObject(swipeView))
} else if (settings.currentMainViewIndex === 2) {
settings.currentMainViewIndex = 0;
}
}
Component {
id: favoritesViewComponent
FavoritesView {
id: favoritesView
width: swipeView.width
height: swipeView.height
property string title: qsTr("My favorites")
EmptyViewPlaceholder {
anchors { left: parent.left; right: parent.right; margins: app.margins }
anchors.verticalCenter: parent.verticalCenter
visible: favoritesView.count === 0 && !Engine.deviceManager.fetchingData
title: qsTr("There are no favorite things yet.")
text: Engine.deviceManager.devices.count === 0 ?
qsTr("It appears there are no things set up either yet. In order to use favorites you need to add some things first.") :
qsTr("Favorites allow you to keep track of your most important things when you have lots of them. Watch out for the star when interacting with things and use it to mark them as your favorites.")
imageSource: "images/starred.svg"
buttonVisible: Engine.deviceManager.devices.count === 0
buttonText: qsTr("Add a thing")
onButtonClicked: pageStack.push(Qt.resolvedUrl("NewDeviceWizard.qml"))
}
}
}
DevicesPage {
property string title: qsTr("My things");
width: swipeView.width
height: swipeView.height
model: InterfacesSortModel {
interfacesModel: InterfacesModel {
devices: Engine.deviceManager.devices
shownInterfaces: app.supportedInterfaces
}
}
EmptyViewPlaceholder {
anchors { left: parent.left; right: parent.right; margins: app.margins }
anchors.verticalCenter: parent.verticalCenter
visible: Engine.deviceManager.devices.count === 0 && !Engine.deviceManager.fetchingData
title: qsTr("Welcome to %1!").arg(app.systemName)
// Have that split in 2 because we need those strings separated in EditDevicesPage too and don't want translators to do them twice
text: qsTr("There are no things set up yet.") + "\n" + qsTr("In order for your %1 box to be useful, go ahead and add some things.").arg(app.systemName)
imageSource: "qrc:/styles/%1/logo.svg".arg(styleController.currentStyle)
buttonText: qsTr("Add a thing")
onButtonClicked: pageStack.push(Qt.resolvedUrl("NewDeviceWizard.qml"))
}
}
ScenesView {
id: scenesView
property string title: qsTr("My scenes");
width: swipeView.width
height: swipeView.height
EmptyViewPlaceholder {
anchors { left: parent.left; right: parent.right; margins: app.margins }
anchors.verticalCenter: parent.verticalCenter
visible: scenesView.count === 0 && !Engine.deviceManager.fetchingData
title: qsTr("There are no scenes set up yet")
text: Engine.deviceManager.devices.count === 0 ?
qsTr("It appears there are no things set up either yet. In order to use scenes you need to add some things first.") :
qsTr("Scenes provide a useful way to control your things with just one click.")
imageSource: "images/slideshow.svg"
buttonText: Engine.deviceManager.devices.count === 0 ? qsTr("Add a thing") : qsTr("Add a scene")
onButtonClicked: {
if (Engine.deviceManager.devices.count === 0) {
pageStack.push(Qt.resolvedUrl("NewDeviceWizard.qml"))
} else {
var page = pageStack.push(Qt.resolvedUrl("MagicPage.qml"))
page.addRule()
}
}
}
}
}
@ -121,91 +184,36 @@ Page {
horizontalAlignment: Text.AlignHCenter
}
}
}
ColumnLayout {
anchors { left: parent.left; right: parent.right; margins: app.margins }
anchors.verticalCenter: parent.verticalCenter
spacing: app.margins * 2
visible: Engine.deviceManager.devices.count === 0 && !Engine.deviceManager.fetchingData
Label {
text: qsTr("Welcome to %1!").arg(app.systemName)
font.pixelSize: app.largeFont
Layout.fillWidth: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
color: app.guhAccent
TabBar {
id: tabBar
Layout.fillWidth: true
Material.elevation: 3
currentIndex: settings.currentMainViewIndex
position: TabBar.Footer
Layout.preferredHeight: 70
// FIXME: All this can go away when we require Controls 2.3 (Qt 5.10) or greater as TabBar got a major rework there.
// Ideally we'd just list the 3 items and set visible to false if the server version isn't good enough but TabBar
// has troubles dealing with that. For now, let's manually fill it and use a timer to initialize the currentIndex.
Component.onCompleted: {
var pi = 0;
if (Engine.jsonRpcClient.ensureServerVersion(1.6)) {
tabEntryComponent.createObject(tabBar, {text: qsTr("Favorites"), iconSource: "../images/starred.svg", pageIndex: pi++})
}
Label {
text: qsTr("There are no things set up yet. In order for your %1 box to be useful, go ahead and add some things.").arg(app.systemName)
Layout.fillWidth: true
Layout.maximumWidth: 400
Layout.alignment: Qt.AlignHCenter
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
Image {
source: "qrc:/styles/%1/logo.svg".arg(styleController.currentStyle)
Layout.preferredWidth: app.iconSize * 5
Layout.preferredHeight: width
Layout.alignment: Qt.AlignHCenter
sourceSize.width: app.iconSize * 5
sourceSize.height: app.iconSize * 5
}
Button {
Layout.fillWidth: true
Layout.maximumWidth: 400
Layout.alignment: Qt.AlignHCenter
text: qsTr("Add a thing")
onClicked: pageStack.push(Qt.resolvedUrl("NewDeviceWizard.qml"))
tabEntryComponent.createObject(tabBar, {text: qsTr("Things"), iconSource: "../images/share.svg", pageIndex: pi++})
tabEntryComponent.createObject(tabBar, {text: qsTr("Scenes"), iconSource: "../images/slideshow.svg", pageIndex: pi++})
initTimer.start()
}
Timer { id: initTimer; interval: 1; repeat: false; onTriggered: tabBar.currentIndex = Qt.binding(function() {return settings.currentMainViewIndex;})}
Component {
id: tabEntryComponent
MainPageTabButton {
property int pageIndex: 0
onClicked: settings.currentMainViewIndex = pageIndex
}
}
}
}
function buildView() {
var shownViews = []
if (page1Model.count > 0) {
shownViews.push(0)
}
if (page2Model.count > 0) {
shownViews.push(1)
}
shownViews.push(2)
if (swipeView.count === shownViews.length) {
return;
}
while (swipeView.count > 0) {
swipeView.removeItem(0)
}
if (shownViews.indexOf(0) >= 0) {
swipeView.addItem(devicePageComponent.createObject(swipeView, {model: page1Model}))
}
if (shownViews.indexOf(1) >= 0) {
swipeView.addItem(devicePageComponent.createObject(swipeView, {model: page2Model}))
}
swipeView.addItem(allDevicesComponent.createObject(swipeView))
}
ColumnLayout {
anchors.fill: parent
SwipeView {
id: swipeView
Layout.fillWidth: true
Layout.fillHeight: true
currentIndex: pageIndicator.currentIndex
clip: true
}
PageIndicator {
id: pageIndicator
Layout.alignment: Qt.AlignHCenter
count: swipeView.count
currentIndex: swipeView.currentIndex
interactive: true
}
}
}

View File

@ -31,6 +31,7 @@ ApplicationWindow {
property bool darkTheme: false
property string graphStyle: "bars"
property string style: "light"
property int currentMainViewIndex: 0
}
Component.onCompleted: {
@ -103,6 +104,12 @@ ApplicationWindow {
}
}
// Workaround flickering on pageStack animations when the white background shines through
Rectangle {
anchors.fill: parent
color: Material.background
}
StackView {
id: pageStack
objectName: "pageStack"
@ -138,6 +145,7 @@ ApplicationWindow {
}
}
property var supportedInterfaces: ["light", "weather", "sensor", "media", "garagegate", "shutter", "garagegate", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"]
function interfaceToString(name) {
switch(name) {
case "light":
@ -168,6 +176,8 @@ ApplicationWindow {
return qsTr("Blinds");
case "garagegate":
return qsTr("Garage gates");
case "uncategorized":
return qsTr("Uncategorized")
}
}
@ -222,6 +232,8 @@ ApplicationWindow {
return Qt.resolvedUrl("images/shutter-10.svg")
case "battery":
return Qt.resolvedUrl("images/battery/battery-050.svg")
case "uncategorized":
return Qt.resolvedUrl("images/select-none.svg")
}
return "";
}
@ -236,6 +248,28 @@ ApplicationWindow {
return "grey";
}
function interfaceListToDevicePage(interfaceList) {
var page;
if (interfaceList.indexOf("media") >= 0) {
page = "MediaDevicePage.qml";
} else if (interfaceList.indexOf("button") >= 0) {
page = "ButtonDevicePage.qml";
} else if (interfaceList.indexOf("weather") >= 0) {
page = "WeatherDevicePage.qml";
} else if (interfaceList.indexOf("sensor") >= 0) {
page = "SensorDevicePage.qml";
} else if (interfaceList.indexOf("inputtrigger") >= 0) {
page = "InputTriggerDevicePage.qml";
} else if (interfaceList.indexOf("shutter") >= 0 ) {
page = "ShutterDevicePage.qml";
} else if (interfaceList.indexOf("garagegate") >= 0 ) {
page = "GarageGateDevicePage.qml";
} else {
page = "GenericDevicePage.qml";
}
return page;
}
Component {
id: invalidVersionComponent
Popup {

View File

@ -0,0 +1,17 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
Menu {
function calculateWidth() {
var result = 0;
var i = 0;
while (itemAt(i) !== null) {
result = Math.max(itemAt(i).contentItem.implicitWidth + app.margins * 2, result);
i++;
}
width = Math.min(parent.width, result + app.margins * 2);
}
onAboutToShow: calculateWidth()
}

View File

@ -0,0 +1,53 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
ColumnLayout {
id: root
spacing: app.margins * 2
property alias title: titleLabel.text
property alias text: textLabel.text
property alias imageSource: image.source
property alias buttonText: button.text
property alias buttonVisible: button.visible
signal imageClicked();
signal buttonClicked();
Label {
id: titleLabel
font.pixelSize: app.largeFont
Layout.fillWidth: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
color: app.guhAccent
}
Label {
id: textLabel
Layout.fillWidth: true
Layout.maximumWidth: 400
Layout.alignment: Qt.AlignHCenter
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
Image {
id: image
Layout.preferredWidth: app.iconSize * 5
Layout.preferredHeight: width
Layout.alignment: Qt.AlignHCenter
sourceSize.width: app.iconSize * 5
sourceSize.height: app.iconSize * 5
MouseArea {
anchors.fill: parent
onClicked: root.imageClicked();
}
}
Button {
id: button
Layout.fillWidth: true
Layout.maximumWidth: 400
Layout.alignment: Qt.AlignHCenter
onClicked: root.buttonClicked();
}
}

View File

@ -0,0 +1,27 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.2
import QtQuick.Layouts 1.3
TabButton {
id: root
property string iconSource
contentItem: ColumnLayout {
ColorIcon {
Layout.preferredWidth: app.iconSize
Layout.preferredHeight: app.iconSize
Layout.alignment: Qt.AlignHCenter
name: root.iconSource
color: root.checked ? app.guhAccent : keyColor
}
Label {
Layout.fillWidth: true
text: root.text
horizontalAlignment: Text.AlignHCenter
font.pixelSize: app.smallFont
color: root.checked ? app.guhAccent : Material.foreground
}
}
}

View File

@ -7,7 +7,8 @@ import "../delegates"
Page {
id: subPage
property alias filterInterface: devicesProxy.filterInterface
property alias shownInterfaces: devicesProxy.shownInterfaces
property alias hiddenInterfaces: devicesProxy.hiddenInterfaces
Component.onCompleted: {
if (devicesProxy.count == 1) {
@ -17,9 +18,12 @@ Page {
header: GuhHeader {
text: {
if (subPage.filterInterface.length > 0) {
if (subPage.shownInterfaces.length === 1) {
return qsTr("My %1 things").arg(interfaceToString(subPage.filterInterface))
} else if (subPage.shownInterfaces.length > 1) {
return qsTr("My things")
}
return qsTr("All my things")
}
@ -32,24 +36,7 @@ Page {
function enterPage(index, replace) {
var device = devicesProxy.get(index);
var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
var page;
if (deviceClass.interfaces.indexOf("media") >= 0) {
page = "MediaDevicePage.qml";
} else if (deviceClass.interfaces.indexOf("button") >= 0) {
page = "ButtonDevicePage.qml";
} else if (deviceClass.interfaces.indexOf("weather") >= 0) {
page = "WeatherDevicePage.qml";
} else if (deviceClass.interfaces.indexOf("sensor") >= 0) {
page = "SensorDevicePage.qml";
} else if (deviceClass.interfaces.indexOf("inputtrigger") >= 0) {
page = "InputTriggerDevicePage.qml";
} else if (deviceClass.interfaces.indexOf("shutter") >= 0 ) {
page = "ShutterDevicePage.qml";
} else if (deviceClass.interfaces.indexOf("garagegate") >= 0 ) {
page = "GarageGateDevicePage.qml";
} else {
page = "GenericDevicePage.qml";
}
var page = app.interfaceListToDevicePage(deviceClass.interfaces);
if (replace) {
pageStack.replace(Qt.resolvedUrl("../devicepages/" + page), {device: devicesProxy.get(index)})
} else {

View File

@ -5,7 +5,7 @@ import Nymea 1.0
import "../components"
Page {
property alias filterInterface: devicesProxy.filterInterface
property alias shownInterfaces: devicesProxy.shownInterfaces
header: GuhHeader {
text: qsTr("Lights")
onBackPressed: pageStack.pop()
@ -48,6 +48,7 @@ Page {
}
delegate: ItemDelegate {
id: itemDelegate
width: parent.width
height: childrenRect.height
property var device: devicesProxy.get(index);
@ -67,9 +68,9 @@ Page {
ThrottledSlider {
id: inlineSlider
visible: model.interfaces.indexOf("dimmablelight") >= 0 && parent.width > 350
property var stateType: deviceClass.stateTypes.findByName("brightness");
property var actionType: deviceClass.actionTypes.findByName("brightness");
property var actionState: device.states.getState(stateType.id)
property var stateType: itemDelegate.deviceClass.stateTypes.findByName("brightness");
property var actionType: itemDelegate.deviceClass.actionTypes.findByName("brightness");
property var actionState: itemDelegate.device.states.getState(stateType.id)
from: 0; to: 100
value: actionState.value
onMoved: {
@ -82,9 +83,9 @@ Page {
}
}
Switch {
property var stateType: deviceClass.stateTypes.findByName("power");
property var actionType: deviceClass.actionTypes.findByName("power");
property var actionState: device.states.getState(stateType.id)
property var stateType: itemDelegate.deviceClass.stateTypes.findByName("power");
property var actionType: itemDelegate.deviceClass.actionTypes.findByName("power");
property var actionState: itemDelegate.device.states.getState(stateType.id)
checked: actionState.value === true
onClicked: {
var params = [];

View File

@ -16,13 +16,55 @@ Page {
onBackPressed: pageStack.pop()
HeaderButton {
imageSource: "../images/magic.svg"
onClicked: pageStack.push(Qt.resolvedUrl("../magic/DeviceRulesPage.qml"), {device: root.device})
imageSource: "../images/navigation-menu.svg"
onClicked: thingMenu.open();
}
}
TagsProxyModel {
id: favoritesProxy
filterDeviceId: root.device.id
filterTagId: "favorites"
}
AutoSizeMenu {
id: thingMenu
x: parent.width - width
Component.onCompleted: {
thingMenu.addItem(menuEntryComponent.createObject(thingMenu, {text: qsTr("Magic"), iconSource: "../images/magic.svg", functionName: "openDeviceMagicPage"}))
thingMenu.addItem(menuEntryComponent.createObject(thingMenu, {text: qsTr("Thing details"), iconSource: "../images/info.svg", functionName: "openDeviceInfoPage"}))
if (Engine.jsonRpcClient.ensureServerVersion(1.6)) {
thingMenu.addItem(menuEntryComponent.createObject(thingMenu,
{
text: Qt.binding(function() { return favoritesProxy.count === 0 ? qsTr("Mark as favorite") : qsTr("Remove from favorites")}),
iconSource: Qt.binding(function() { return favoritesProxy.count === 0 ? "../images/starred.svg" : "../images/non-starred.svg"}),
functionName: "toggleFavorite"
}))
}
}
function openDeviceMagicPage() {
pageStack.push(Qt.resolvedUrl("../magic/DeviceRulesPage.qml"), {device: root.device})
}
function openDeviceInfoPage() {
pageStack.push(Qt.resolvedUrl("GenericDeviceStateDetailsPage.qml"), {device: root.device})
}
function toggleFavorite() {
if (favoritesProxy.count === 0) {
Engine.tagsManager.tagDevice(root.device.id, "favorites", 100000)
} else {
Engine.tagsManager.untagDevice(root.device.id, "favorites")
}
}
HeaderButton {
imageSource: "../images/info.svg"
onClicked: pageStack.push(Qt.resolvedUrl("GenericDeviceStateDetailsPage.qml"), {device: root.device})
Component {
id: menuEntryComponent
IconMenuItem {
width: parent.width
property string functionName: ""
onTriggered: thingMenu[functionName]()
}
}
}

View File

@ -5,22 +5,8 @@ import Nymea 1.0
import "../components"
import "../customviews"
Page {
DevicePageBase {
id: root
property var device: null
readonly property var deviceClass: Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId)
header: GuhHeader {
text: device.name
onBackPressed: pageStack.pop()
HeaderButton {
imageSource: "../images/info.svg"
onClicked: pageStack.push(Qt.resolvedUrl("GenericDeviceStateDetailsPage.qml"), {device: root.device})
}
}
ColumnLayout {
id: contentColumn

View File

@ -1,301 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg4874"
version="1.1"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
viewBox="0 0 96 96.000001"
sodipodi:docname="magic.svg">
<defs
id="defs4876" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8284272"
inkscape:cx="72.011163"
inkscape:cy="30.205758"
inkscape:document-units="px"
inkscape:current-layer="g4780"
showgrid="false"
showborder="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
showguides="false"
inkscape:guide-bbox="true"
inkscape:snap-global="true"
inkscape:window-width="2880"
inkscape:window-height="1698"
inkscape:window-x="0"
inkscape:window-y="44"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid5451"
empspacing="8"
snapvisiblegridlinesonly="true"
enabled="true" />
<sodipodi:guide
orientation="1,0"
position="8,-8.0000001"
id="guide4063"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="4,-8.0000001"
id="guide4065"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-8,88.000001"
id="guide4067"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-8,92.000001"
id="guide4069"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="104,4"
id="guide4071"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-5,8.0000001"
id="guide4073"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="92,-8.0000001"
id="guide4075"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="88,-8.0000001"
id="guide4077"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-8,84.000001"
id="guide4074"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="12,-8.0000001"
id="guide4076"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-5,12"
id="guide4078"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="84,-9.0000001"
id="guide4080"
inkscape:locked="false" />
<sodipodi:guide
position="48,-8.0000001"
orientation="1,0"
id="guide4170"
inkscape:locked="false" />
<sodipodi:guide
position="-8,48"
orientation="0,1"
id="guide4172"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata4879">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(67.857146,-78.50504)">
<g
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
id="g4845"
style="display:inline">
<g
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="next01.png"
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
id="g4778"
inkscape:label="Layer 1">
<g
transform="matrix(-1,0,0,1,575.99999,611)"
id="g4780"
style="display:inline">
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
id="rect4782"
width="96.037987"
height="96"
x="-438.00244"
y="345.36221"
transform="scale(-1,1)" />
<g
id="g1894"
transform="matrix(0.8660254,0.49980225,-0.50019783,0.8660254,249.00681,-142.21407)">
<path
style="fill:none;fill-opacity:1;stroke:#808080;stroke-width:3.00094485;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 30.308594 7.9980469 C 29.732649 7.9577855 29.137407 8.0829062 28.597656 8.3945312 L 23.402344 11.394531 C 21.96301 12.225531 21.473687 14.052853 22.304688 15.492188 L 40.070312 46.263672 C 42.564163 45.825188 45.083421 45.27706 47.589844 44.605469 C 49.160416 44.184634 50.68081 43.717115 52.173828 43.230469 L 32.695312 9.4921875 C 32.175937 8.5926036 31.268502 8.0651491 30.308594 7.9980469 z M 52.939453 44.556641 C 51.325707 45.088407 49.681399 45.5984 47.978516 46.054688 C 45.608831 46.68964 43.228725 47.217784 40.869141 47.648438 L 42.587891 50.625 C 45.343918 50.105213 48.154312 49.47698 51.005859 48.712891 C 52.265992 48.375244 53.496223 48.01012 54.71875 47.638672 L 52.939453 44.556641 z M 55.492188 48.976562 C 54.146813 49.389444 52.784982 49.789545 51.394531 50.162109 C 48.681709 50.88902 46.00943 51.484656 43.378906 51.994141 L 63.304688 86.507812 C 64.135688 87.947147 65.96301 88.436469 67.402344 87.605469 L 72.597656 84.605469 C 74.03699 83.774469 74.526313 81.947147 73.695312 80.507812 L 55.492188 48.976562 z "
transform="matrix(-0.50019783,-0.8660254,-0.86636805,0.5,455.57862,410.93144)"
id="rect1888" />
<path
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.00094485;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 38.195312,19.019531 -10.390624,6 12.265624,21.244141 c 2.493851,-0.438484 5.013109,-0.986612 7.519532,-1.658203 1.570572,-0.420835 3.090966,-0.888354 4.583984,-1.375 z m 17.296876,29.957031 c -1.345375,0.412882 -2.707206,0.812983 -4.097657,1.185547 -2.712822,0.726911 -5.385101,1.322547 -8.015625,1.832032 l 14.425782,24.986328 10.390624,-6 z"
id="rect1890"
transform="matrix(-0.50019783,-0.8660254,-0.86636805,0.5,455.57862,410.93144)"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccccccc" />
</g>
<path
sodipodi:type="star"
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1903"
sodipodi:sides="5"
sodipodi:cx="12"
sodipodi:cy="63.807842"
sodipodi:r1="11.632512"
sodipodi:r2="5.8162556"
sodipodi:arg1="1.3326172"
sodipodi:arg2="1.9609357"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 14.7445,75.111959 -4.9565227,-5.924918 -7.6907342,0.72414 4.1032818,-6.544833 -3.0652651,-7.090552 7.4924902,1.879988 5.796296,-5.106341 0.527332,7.70673 6.647573,3.934659 -7.166581,2.883033 z"
transform="matrix(0,-1,-1.0003957,0,433.33151,433.80227)"
inkscape:transform-center-x="-0.84809303"
inkscape:transform-center-y="0.27286362" />
<path
sodipodi:type="star"
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1905"
sodipodi:sides="5"
sodipodi:cx="14.86737"
sodipodi:cy="34.318844"
sodipodi:r1="6.986371"
sodipodi:r2="3.4931855"
sodipodi:arg1="0.97303794"
sodipodi:arg2="1.6013565"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 18.799236,40.093773 -4.038602,-2.283375 -4.170535,2.032424 0.923622,-4.54654 -3.2217163,-3.338361 4.6094313,-0.526542 2.179405,-4.095644 1.925164,4.22112 4.568662,0.807113 -3.419615,3.135337 z"
transform="matrix(0,-1,-1.0003957,0,435.29822,435.88751)"
inkscape:transform-center-x="-0.065962241"
inkscape:transform-center-y="-0.6040865" />
<path
sodipodi:type="star"
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1907"
sodipodi:sides="5"
sodipodi:cx="69.667763"
sodipodi:cy="48"
sodipodi:r1="7.9908891"
sodipodi:r2="3.9954443"
sodipodi:arg1="0.86871607"
sodipodi:arg2="1.4970346"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 74.828338,54.101047 -4.866132,-2.116467 -4.502178,2.808746 0.509162,-5.28199 -4.062526,-3.413877 5.180811,-1.147982 1.9914,-4.918638 2.692755,4.572498 5.293279,0.373992 -3.516597,3.973941 z"
transform="matrix(0,-1,-1.0003957,0,423.1292,432.66883)"
inkscape:transform-center-x="0.18198021"
inkscape:transform-center-y="-0.58791341" />
<path
sodipodi:type="star"
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1909"
sodipodi:sides="5"
sodipodi:cx="8"
sodipodi:cy="11.999999"
sodipodi:r1="7.0902119"
sodipodi:r2="3.5451059"
sodipodi:arg1="0.97138318"
sodipodi:arg2="1.5997017"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 12,17.854152 7.8975417,15.543624 3.6684378,17.613258 4.598151,12.997596 1.3229474,9.6150309 6,9.0729227 8.2049168,4.912749 l 1.9608642,4.2806207 4.637917,0.8114363 -3.465172,3.187677 z"
transform="matrix(0,-1,-1.0003957,0,435.19471,439.528)"
inkscape:transform-center-x="-0.06331877"
inkscape:transform-center-y="-0.61654618" />
<path
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 67.513672 10.917969 C 64.985843 10.916324 62.314284 11.045287 59.537109 11.310547 C 56.933931 11.559183 54.230913 11.939606 51.466797 12.429688 L 54.220703 15.048828 L 54.150391 14.304688 C 58.555851 13.883874 62.636049 13.890175 66.193359 14.306641 C 69.750679 14.723066 72.787297 15.544697 75.117188 16.794922 C 77.447077 18.045147 79.094458 19.763523 79.673828 21.923828 C 80.253028 24.084403 79.685229 26.394797 78.292969 28.642578 C 76.900689 30.890369 74.681094 33.122225 71.808594 35.261719 C 66.063594 39.540696 57.688946 43.452777 47.978516 46.054688 C 38.262796 48.657997 28.345412 49.532279 20.576172 48.769531 C 16.691562 48.388152 13.343288 47.602974 10.798828 46.375 C 8.2543681 45.147036 6.4596556 43.424899 5.9785156 41.189453 C 5.5911256 39.389645 6.2567856 37.391465 7.3847656 35.351562 C 8.5127356 33.31166 10.146742 31.211204 11.919922 29.259766 C 13.049542 28.016587 14.196023 27.034758 15.345703 25.978516 C 14.198123 26.64999 13.202239 27.096549 11.849609 28.130859 C 8.3114594 30.836369 5.6074594 33.583556 3.9746094 36.195312 C 2.3417594 38.807069 1.78282 41.231781 2.375 43.441406 C 2.96752 45.653121 4.6644394 47.502934 7.3808594 48.960938 C 10.097289 50.418941 13.808222 51.447488 18.226562 51.964844 C 27.063252 52.999564 38.717849 52.005538 51.005859 48.712891 C 63.293889 45.420373 73.884061 40.453139 81.019531 35.138672 C 84.587271 32.481433 87.286616 29.736177 88.910156 27.115234 C 90.533686 24.494281 91.079048 22.043686 90.486328 19.832031 C 89.893818 17.620326 88.196889 15.770503 85.480469 14.3125 C 82.764049 12.854487 79.053106 11.827912 74.634766 11.310547 C 72.425596 11.051864 70.041501 10.919613 67.513672 10.917969 z "
id="path1945"
transform="matrix(0,-1,-1.0003957,0,438.00245,441.36222)" />
<path
sodipodi:type="star"
style="fill:#808080;fill-opacity:1;stroke:#ffffff;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1970"
sodipodi:sides="5"
sodipodi:cx="46.3125"
sodipodi:cy="5.5"
sodipodi:r1="13.101168"
sodipodi:r2="6.5505843"
sodipodi:arg1="0.51914607"
sodipodi:arg2="1.1474646"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 57.687499,11.999999 -8.684017,-0.527665 -5.35778,6.854543 -2.18167,-8.4220481 -8.174703,-2.9773817 7.335671,-4.6774469 0.305536,-8.6946667 6.715363,5.53122727 8.363535,-2.39621797 -3.185348,8.0959334 z"
transform="matrix(0,-1,-1.0003957,0,430.32036,431.20152)"
inkscape:transform-center-x="0.82409001"
inkscape:transform-center-y="0.44110773" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff3dff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.50029671;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 416.48828,349.14453 c -2.09199,-0.0241 -4.26,0.70397 -6.40039,2.0293 -2.85386,1.76711 -5.7029,4.5987 -8.43945,8.27148 -5.47311,7.34556 -10.49257,18.07774 -13.82813,30.52149 -3.33566,12.44372 -4.35859,24.24678 -3.29297,33.34375 0.53281,4.54848 1.58618,8.42211 3.17383,11.3789 1.58765,2.9568 3.75008,5.02172 6.45508,5.7461 2.70697,0.72518 5.5914,-0.002 8.43164,-1.77735 2.84024,-1.77499 5.68531,-4.60085 8.46484,-8.23437 3.85709,-5.04215 5.06812,-9.08101 5.65235,-10.48633 h -0.002 c 5.9e-4,-0.001 0.003,-0.004 0.004,-0.006 l -1.38867,-0.56836 c -5e-4,0.001 -0.001,0.005 -0.002,0.006 -2.1e-4,5e-4 -0.002,0.001 -0.002,0.002 -0.25972,0.64229 -1.2251,2.1002 -2.58203,3.73438 -1.35774,1.63516 -3.11592,3.50639 -5.00976,5.22656 -1.89385,1.72018 -3.92855,3.2907 -5.8125,4.33203 -1.88396,1.04133 -3.599,1.50968 -4.79688,1.25196 -1.6406,-0.35298 -3.0406,-1.70314 -4.15234,-4.00586 -1.11175,-2.30272 -1.88397,-5.50482 -2.25391,-9.27149 -0.73988,-7.53333 0.11212,-17.31777 2.67188,-26.86719 2.5612,-9.55471 6.42665,-17.78736 10.55078,-23.32226 2.06206,-2.76745 4.19024,-4.85677 6.20703,-6.10547 2.01678,-1.2487 3.87589,-1.65428 5.54492,-1.20703 1.66882,0.44738 3.07556,1.72879 4.19727,3.81836 1.1217,2.08956 1.91896,4.96147 2.32031,8.38867 0.40135,3.42719 0.41248,7.40815 0,11.72461 l 1.49219,0.14258 c 0.42098,-4.40546 0.41463,-8.48566 -0.002,-12.04297 -0.41659,-3.55732 -1.23756,-6.59394 -2.48828,-8.92383 -1.25072,-2.32989 -2.9697,-3.97727 -5.13086,-4.55664 -2.16143,-0.5792 -4.47398,-0.0114 -6.72265,1.38086 -2.24868,1.39228 -4.48076,3.61187 -6.6211,6.48437 -4.28067,5.745 -8.19393,14.11965 -10.79687,23.83008 -2.60434,9.71572 -3.47985,19.63311 -2.7168,27.40235 0.38153,3.88461 1.16803,7.23288 2.39649,9.77734 1.22845,2.54446 2.95117,4.33917 5.1875,4.82031 1.80052,0.38739 3.79913,-0.27827 5.83984,-1.40625 2.04071,-1.12797 4.14154,-2.76197 6.09375,-4.53515 1.24367,-1.12962 2.22654,-2.27611 3.2832,-3.42579 -0.67174,1.14758 -1.11762,2.14347 -2.15234,3.4961 -2.70658,3.53815 -5.45557,6.24215 -8.06836,7.875 -2.61279,1.63285 -5.0395,2.19179 -7.25,1.59961 -2.21259,-0.59252 -4.06291,-2.28944 -5.52149,-5.00586 -1.45858,-2.71643 -2.48634,-6.42736 -3.0039,-10.8457 -1.03513,-8.83669 -0.042,-20.49129 3.25195,-32.7793 3.29382,-12.28803 8.26351,-22.8782 13.58008,-30.01367 2.65829,-3.56774 5.40536,-6.26709 8.02734,-7.89063 2.62199,-1.62353 5.07263,-2.16889 7.28516,-1.57617 2.21258,0.59251 4.0629,2.28944 5.52148,5.00586 1.45859,2.71642 2.48634,6.42736 3.00391,10.8457 0.51757,4.41834 0.53073,9.54331 0,15.09766 -0.26536,2.77718 -0.66654,5.66077 -1.20703,8.61914 l 1.47656,0.26953 c 0.54791,-2.99895 0.95499,-5.92441 1.22461,-8.74609 0.53923,-5.64335 0.52891,-10.86754 -0.004,-15.41602 -0.53281,-4.54849 -1.58422,-8.42406 -3.17187,-11.38086 -1.58765,-2.95679 -3.75007,-5.01976 -6.45508,-5.74414 -0.67623,-0.18116 -1.36517,-0.2732 -2.0625,-0.28125 z"
id="path943"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
id="path958"
d="m 416.48828,349.14453 c -2.09199,-0.0241 -4.26,0.70397 -6.40039,2.0293 -2.85386,1.76711 -5.7029,4.5987 -8.43945,8.27148 -5.47311,7.34556 -10.49257,18.07774 -13.82813,30.52149 -3.33566,12.44372 -4.35859,24.24678 -3.29297,33.34375 0.53281,4.54848 1.58618,8.42211 3.17383,11.3789 1.58765,2.9568 3.75008,5.02172 6.45508,5.7461 2.70697,0.72518 5.5914,-0.002 8.43164,-1.77735 2.84024,-1.77499 5.68531,-4.60085 8.46484,-8.23437 3.85709,-5.04215 5.06812,-9.08101 5.65235,-10.48633 h -0.002 c 5.9e-4,-0.001 0.003,-0.004 0.004,-0.006 l -1.38867,-0.56836 c -5e-4,0.001 -0.001,0.005 -0.002,0.006 -2.1e-4,5e-4 -0.002,0.001 -0.002,0.002 -0.25972,0.64229 -1.2251,2.1002 -2.58203,3.73438 -1.35774,1.63516 -3.11592,3.50639 -5.00976,5.22656 -1.89385,1.72018 -3.92855,3.2907 -5.8125,4.33203 -1.88396,1.04133 -3.599,1.50968 -4.79688,1.25196 -1.6406,-0.35298 -3.0406,-1.70314 -4.15234,-4.00586 -1.11175,-2.30272 -1.88397,-5.50482 -2.25391,-9.27149 -0.73988,-7.53333 0.11212,-17.31777 2.67188,-26.86719 2.5612,-9.55471 6.42665,-17.78736 10.55078,-23.32226 2.06206,-2.76745 4.19024,-4.85677 6.20703,-6.10547 2.01678,-1.2487 3.87589,-1.65428 5.54492,-1.20703 1.66882,0.44738 3.07556,1.72879 4.19727,3.81836 1.1217,2.08956 1.91896,4.96147 2.32031,8.38867 0.40135,3.42719 0.41248,7.40815 0,11.72461 l 1.49219,0.14258 c 0.42098,-4.40546 0.41463,-8.48566 -0.002,-12.04297 -0.41659,-3.55732 -1.23756,-6.59394 -2.48828,-8.92383 -1.25072,-2.32989 -2.9697,-3.97727 -5.13086,-4.55664 -2.16143,-0.5792 -4.47398,-0.0114 -6.72265,1.38086 -2.24868,1.39228 -4.48076,3.61187 -6.6211,6.48437 -4.28067,5.745 -8.19393,14.11965 -10.79687,23.83008 -2.60434,9.71572 -3.47985,19.63311 -2.7168,27.40235 0.38153,3.88461 1.16803,7.23288 2.39649,9.77734 1.22845,2.54446 2.95117,4.33917 5.1875,4.82031 1.80052,0.38739 3.79913,-0.27827 5.83984,-1.40625 2.04071,-1.12797 4.14154,-2.76197 6.09375,-4.53515 1.24367,-1.12962 2.22654,-2.27611 3.2832,-3.42579 -0.67174,1.14758 -1.11762,2.14347 -2.15234,3.4961 -2.70658,3.53815 -5.45557,6.24215 -8.06836,7.875 -2.61279,1.63285 -5.0395,2.19179 -7.25,1.59961 -2.21259,-0.59252 -4.06291,-2.28944 -5.52149,-5.00586 -1.45858,-2.71643 -2.48634,-6.42736 -3.0039,-10.8457 -1.03513,-8.83669 -0.042,-20.49129 3.25195,-32.7793 3.29382,-12.28803 8.26351,-22.8782 13.58008,-30.01367 2.65829,-3.56774 5.40536,-6.26709 8.02734,-7.89063 2.62199,-1.62353 5.07263,-2.16889 7.28516,-1.57617 2.21258,0.59251 4.0629,2.28944 5.52148,5.00586 1.45859,2.71642 2.48634,6.42736 3.00391,10.8457 0.51757,4.41834 0.53073,9.54331 0,15.09766 -0.26536,2.77718 -0.66654,5.66077 -1.20703,8.61914 l 1.47656,0.26953 c 0.54791,-2.99895 0.95499,-5.92441 1.22461,-8.74609 0.53923,-5.64335 0.52891,-10.86754 -0.004,-15.41602 -0.53281,-4.54849 -1.58422,-8.42406 -3.17187,-11.38086 -1.58765,-2.95679 -3.75007,-5.01976 -6.45508,-5.74414 -0.67623,-0.18116 -1.36517,-0.2732 -2.0625,-0.28125 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#003dff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.50029671;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg4874"
version="1.1"
inkscape:version="0.91+devel r"
viewBox="0 0 96 96.000001"
sodipodi:docname="non-starred.svg">
<defs
id="defs4876" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6199993"
inkscape:cx="-4.3416438"
inkscape:cy="69.76867"
inkscape:document-units="px"
inkscape:current-layer="g4780"
showgrid="true"
showborder="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-global="true">
<inkscape:grid
type="xygrid"
id="grid5451"
empspacing="8" />
<sodipodi:guide
orientation="1,0"
position="8,-8.0000001"
id="guide4063" />
<sodipodi:guide
orientation="1,0"
position="4,-8.0000001"
id="guide4065" />
<sodipodi:guide
orientation="0,1"
position="-8,88.000001"
id="guide4067" />
<sodipodi:guide
orientation="0,1"
position="-8,92.000001"
id="guide4069" />
<sodipodi:guide
orientation="0,1"
position="104,4"
id="guide4071" />
<sodipodi:guide
orientation="0,1"
position="-5,8.0000001"
id="guide4073" />
<sodipodi:guide
orientation="1,0"
position="88,-8.0000001"
id="guide4077" />
<sodipodi:guide
orientation="0,1"
position="-8,84.000001"
id="guide4074" />
<sodipodi:guide
orientation="1,0"
position="12,-8.0000001"
id="guide4076" />
<sodipodi:guide
orientation="1,0"
position="84,-8.0000001"
id="guide4080" />
<sodipodi:guide
position="48,-8.0000001"
orientation="1,0"
id="guide4170" />
<sodipodi:guide
position="-8,48"
orientation="0,1"
id="guide4172" />
<sodipodi:guide
position="92,-8.0000001"
orientation="1,0"
id="guide4760" />
</sodipodi:namedview>
<metadata
id="metadata4879">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(67.857146,-78.50504)">
<g
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
id="g4845"
style="display:inline">
<g
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="next01.png"
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
id="g4778"
inkscape:label="Layer 1">
<g
transform="matrix(-1,0,0,1,575.99999,611)"
id="g4780"
style="display:inline">
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
id="rect4782"
width="96.037987"
height="96"
x="-438.00244"
y="345.36221"
transform="scale(-1,1)" />
<path
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
d="M 48.021484 8 C 48.021484 8 48.023438 8.0019531 48.023438 8.0019531 C 48.023438 8.0019531 48.025391 8 48.025391 8 L 48.021484 8 z M 48.023438 8.0019531 C 48.020658 8.0024345 48.017186 8.0030924 48.015625 8.0078125 C 48.008625 8.0088121 48.007859 8.0126262 48.005859 8.015625 C 47.999859 8.0186238 47.999047 8.0243449 47.998047 8.0273438 C 47.992047 8.0303426 47.991281 8.0292047 47.988281 8.0332031 C 42.927951 18.893186 39.018172 29.962354 36.794922 36.744141 C 29.481092 36.750138 17.434385 37.027378 5.953125 38.517578 C 5.952125 38.521577 5.9510781 38.522392 5.9550781 38.525391 C 5.9540781 38.532388 5.9598906 38.536164 5.9628906 38.539062 C 5.9638906 38.54606 5.96575 38.548782 5.96875 38.550781 C 5.96975 38.557778 5.9706562 38.555595 5.9726562 38.558594 C 14.737316 46.727322 24.057332 53.865858 29.820312 58.076172 C 27.565103 65.033519 24.105319 76.578677 21.974609 87.958984 C 21.977609 87.960984 21.979375 87.961983 21.984375 87.958984 C 21.990375 87.961983 21.993194 87.956078 21.996094 87.955078 C 22.003094 87.956078 22.007866 87.954125 22.009766 87.953125 C 22.016766 87.954125 22.016531 87.954125 22.019531 87.953125 C 32.497441 82.141414 42.168135 75.48841 47.953125 71.308594 C 53.873275 75.603464 63.783426 82.459396 73.947266 88.001953 C 73.950266 87.999954 73.950219 87.995332 73.949219 87.990234 C 73.953219 87.986236 73.951172 87.979461 73.951172 87.976562 C 73.954172 87.970565 73.953125 87.966743 73.953125 87.964844 C 73.956125 87.958846 73.958984 87.957223 73.958984 87.953125 C 71.669554 76.192038 68.326252 64.939966 66.138672 58.146484 C 72.052872 53.843157 81.633422 46.537088 90.044922 38.583984 C 90.043922 38.580986 90.043109 38.579125 90.037109 38.578125 C 90.034109 38.572127 90.028391 38.571312 90.025391 38.570312 C 90.021391 38.566314 90.013419 38.565453 90.011719 38.564453 C 90.007719 38.560455 90.008906 38.556687 90.003906 38.554688 C 78.111466 37.097764 66.377304 36.800063 59.240234 36.78125 C 56.975133 29.827029 52.986923 18.457674 48.023438 8.0019531 z M 48.021484 18.292969 C 51.110304 25.766972 53.823267 33.067625 55.435547 38.017578 L 56.332031 40.773438 L 59.228516 40.78125 C 64.336616 40.794715 72.076045 41.124943 80.265625 41.757812 C 74.107185 47.009426 67.995863 51.846816 63.783203 54.912109 L 61.441406 56.617188 L 62.330078 59.371094 C 63.895448 64.2323 65.971861 71.694193 67.900391 79.677734 C 61.005081 75.444679 54.517604 71.128064 50.302734 68.070312 L 47.957031 66.371094 L 45.607422 68.066406 C 41.469542 71.056143 35.017477 75.335426 28.023438 79.634766 C 29.919048 71.768427 32.01918 64.262584 33.625 59.308594 L 34.517578 56.556641 L 32.179688 54.847656 C 28.056207 51.835128 21.990151 47.021389 15.738281 41.697266 C 23.804281 41.069284 31.589045 40.746188 36.796875 40.742188 L 39.693359 40.740234 L 40.595703 37.988281 C 42.186103 33.136911 44.890674 25.882296 48.021484 18.292969 z "
transform="matrix(0,-1,-1.0003957,0,438.00245,441.36222)"
id="path4170" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -0,0 +1,167 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg4874"
version="1.1"
inkscape:version="0.91+devel r"
viewBox="0 0 96 96.000001"
sodipodi:docname="slideshow.svg">
<defs
id="defs4876" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4.6116859"
inkscape:cx="-47.395774"
inkscape:cy="41.875405"
inkscape:document-units="px"
inkscape:current-layer="g4778"
showgrid="true"
showborder="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
showguides="true"
inkscape:guide-bbox="true">
<inkscape:grid
type="xygrid"
id="grid5451"
empspacing="8" />
<sodipodi:guide
orientation="1,0"
position="8,-8.0000001"
id="guide4063" />
<sodipodi:guide
orientation="1,0"
position="4,-8.0000001"
id="guide4065" />
<sodipodi:guide
orientation="0,1"
position="-8,88.000001"
id="guide4067" />
<sodipodi:guide
orientation="0,1"
position="-8,92.000001"
id="guide4069" />
<sodipodi:guide
orientation="0,1"
position="104,4"
id="guide4071" />
<sodipodi:guide
orientation="0,1"
position="-5,8.0000001"
id="guide4073" />
<sodipodi:guide
orientation="1,0"
position="92,-8.0000001"
id="guide4075" />
<sodipodi:guide
orientation="1,0"
position="88,-8.0000001"
id="guide4077" />
<sodipodi:guide
orientation="0,1"
position="-8,84.000001"
id="guide4074" />
<sodipodi:guide
orientation="1,0"
position="12,-8.0000001"
id="guide4076" />
<sodipodi:guide
orientation="0,1"
position="-5,12"
id="guide4078" />
<sodipodi:guide
orientation="1,0"
position="84,-9.0000001"
id="guide4080" />
<sodipodi:guide
position="48,-8.0000001"
orientation="1,0"
id="guide4170" />
<sodipodi:guide
position="-8,48"
orientation="0,1"
id="guide4172" />
</sodipodi:namedview>
<metadata
id="metadata4879">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(67.857146,-78.50504)">
<g
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
id="g4845"
style="display:inline">
<g
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="next01.png"
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
id="g4778"
inkscape:label="Layer 1">
<path
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.31292856;marker:none;enable-background:accumulate"
d="m 158.25437,1028.3622 43.76848,0 c 0,0 -10.33241,-23.0909 -21.88306,-38.89082 -11.55061,15.79992 -21.88542,38.89082 -21.88542,38.89082 z"
id="path4188"
inkscape:connector-curvature="0" />
<rect
y="956.36218"
x="137.99754"
height="96"
width="96.037987"
id="rect4782"
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079107;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 146.00195,1044.3633 2,0 66.02539,0 0,-68.00197 -68.02539,0 0,68.00197 z m 4,-4.002 0,-59.99802 60.02539,0 0,59.99802 -60.02539,0 z"
id="rect4177"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079107;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 166.00977,964.36133 0,14 0,2.00195 4,0 0,-2.00195 0,-9.99805 52.02148,0 0,53.99802 0,2.002 4,0 0,-2.002 0,-57.99997 -60.02148,0 z"
id="path4182"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg4874"
version="1.1"
inkscape:version="0.91+devel r"
viewBox="0 0 96 96.000001"
sodipodi:docname="starred.svg">
<defs
id="defs4876" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6199993"
inkscape:cx="-4.3416438"
inkscape:cy="69.76867"
inkscape:document-units="px"
inkscape:current-layer="g4780"
showgrid="true"
showborder="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-global="true">
<inkscape:grid
type="xygrid"
id="grid5451"
empspacing="8" />
<sodipodi:guide
orientation="1,0"
position="8,-8.0000001"
id="guide4063" />
<sodipodi:guide
orientation="1,0"
position="4,-8.0000001"
id="guide4065" />
<sodipodi:guide
orientation="0,1"
position="-8,88.000001"
id="guide4067" />
<sodipodi:guide
orientation="0,1"
position="-8,92.000001"
id="guide4069" />
<sodipodi:guide
orientation="0,1"
position="104,4"
id="guide4071" />
<sodipodi:guide
orientation="0,1"
position="-5,8.0000001"
id="guide4073" />
<sodipodi:guide
orientation="1,0"
position="88,-8.0000001"
id="guide4077" />
<sodipodi:guide
orientation="0,1"
position="-8,84.000001"
id="guide4074" />
<sodipodi:guide
orientation="1,0"
position="12,-8.0000001"
id="guide4076" />
<sodipodi:guide
orientation="1,0"
position="84,-8.0000001"
id="guide4080" />
<sodipodi:guide
position="48,-8.0000001"
orientation="1,0"
id="guide4170" />
<sodipodi:guide
position="-8,48"
orientation="0,1"
id="guide4172" />
<sodipodi:guide
position="92,-8.0000001"
orientation="1,0"
id="guide4760" />
</sodipodi:namedview>
<metadata
id="metadata4879">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(67.857146,-78.50504)">
<g
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
id="g4845"
style="display:inline">
<g
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="next01.png"
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
id="g4778"
inkscape:label="Layer 1">
<g
transform="matrix(-1,0,0,1,575.99999,611)"
id="g4780"
style="display:inline">
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
id="rect4782"
width="96.037987"
height="96"
x="-438.00244"
y="345.36221"
transform="scale(-1,1)" />
<path
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;enable-background:accumulate"
d="m 429.99929,393.33773 c 0,0.004 -9.5e-4,0.007 -0.007,0.009 -0.001,0.007 -0.006,0.007 -0.009,0.009 -0.003,0.006 -0.008,0.008 -0.011,0.009 -0.003,0.006 -0.003,0.006 -0.007,0.009 -10.86428,5.06033 -21.9364,8.97057 -28.72087,11.19382 -0.006,7.31383 -0.28348,19.36 -1.77427,30.84126 -0.004,0.001 -0.006,0.002 -0.009,-0.002 -0.007,10e-4 -0.01,-0.004 -0.0129,-0.007 -0.007,-0.001 -0.009,-0.004 -0.011,-0.007 -0.007,-0.001 -0.006,-0.002 -0.009,-0.004 -8.17196,-8.76466 -15.31373,-18.08322 -19.52571,-23.8462 -6.9601,2.25521 -18.50812,5.71446 -29.89293,7.84517 -0.002,-0.003 -0.003,-0.004 0,-0.009 -0.003,-0.006 0.001,-0.01 0.002,-0.0129 -0.001,-0.007 0.001,-0.011 0.002,-0.0129 -0.001,-0.007 -0.001,-0.008 0,-0.011 5.81401,-10.47791 12.47128,-20.14765 16.65275,-25.93264 -4.29657,-5.92015 -11.1555,-15.83137 -16.70025,-25.99521 0.002,-0.003 0.006,-0.003 0.0111,-0.002 0.004,-0.004 0.01,-0.002 0.0129,-0.002 0.006,-0.003 0.011,-0.002 0.0129,-0.002 0.006,-0.003 0.007,-0.004 0.0111,-0.004 11.76574,2.28943 23.02338,5.63169 29.81955,7.81927 4.30503,-5.9142 11.61263,-15.49511 19.56888,-23.90661 0.003,0.001 0.006,0.003 0.007,0.009 0.006,0.003 0.006,0.008 0.007,0.011 0.004,0.004 0.006,0.0112 0.007,0.0129 0.004,0.004 0.007,0.004 0.009,0.009 1.4575,11.89244 1.75546,23.62651 1.77428,30.76358 6.95749,2.26527 18.3316,6.25356 28.7921,11.21756 z"
id="path4170"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -12,10 +12,14 @@ Page {
readonly property bool isEventBased: rule.eventDescriptors.count > 0 || rule.timeDescriptor.timeEventItems.count > 0
readonly property bool isStateBased: (rule.stateEvaluator !== null || rule.timeDescriptor.calendarItems.count > 0) && !isEventBased
readonly property bool actionsVisible: !isEmpty
readonly property bool actionsVisible: true
readonly property bool exitActionsVisible: actionsVisible && isStateBased
readonly property bool hasActions: rule.actions.count > 0
readonly property bool hasExitActions: rule.exitActions.count > 0
readonly property bool isEmpty: !isEventBased && !isStateBased
readonly property bool isEmpty: !isEventBased && !isStateBased && !hasActions
property string ruleIcon: Engine.tagsManager.tags.findRuleTag(rule.id, "icon").value
property string ruleColor: Engine.tagsManager.tags.findRuleTag(rule.id, "color").value
signal accept();
signal cancel();
@ -223,6 +227,7 @@ Page {
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
Layout.topMargin: app.margins
visible: !root.isEmpty
property bool showDetails: false
@ -268,6 +273,101 @@ Page {
}
}
}
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: ruleSettings.showDetails ? implicitHeight : 0
opacity: ruleSettings.showDetails ? 1 : 0
Behavior on Layout.preferredHeight { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad} }
Behavior on opacity { NumberAnimation {duration: 200; easing.type: Easing.InOutQuad } }
Label {
Layout.fillWidth: true
text: qsTr("This is a scene" + root.ruleColor, root.ruleIcon)
}
CheckBox {
checked: root.rule.executable
onClicked: {
root.rule.executable = checked
}
}
}
GridLayout {
id: colorsGrid
Layout.fillWidth: true
columns: (root.width / 10 < app.iconSize + app.margins) ? 5 : 10
columnSpacing: app.margins
rowSpacing: app.margins
Layout.preferredHeight: opacity > 0 ? implicitHeight : 0
opacity: Engine.jsonRpcClient.ensureServerVersion(1.6) && ruleSettings.showDetails && root.rule.executable ? 1 : 0
Behavior on Layout.preferredHeight { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad} }
Behavior on opacity { NumberAnimation {duration: 200; easing.type: Easing.InOutQuad } }
Repeater {
model: ["red", "orange", "yellow", "lime", "green", "aqua", "skyblue", "blue", "magenta", "purple"]
delegate: Item {
Layout.fillWidth: true
Layout.preferredHeight: app.iconSize + app.margins
Rectangle {
height: parent.height
width: height
color: "transparent"
border.width: 2
border.color: modelData === root.ruleColor ? app.guhAccent : "transparent"
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: {
root.ruleColor = modelData
}
}
ColorIcon {
height: app.iconSize
width: app.iconSize
color: modelData
name: "../images/" + (root.ruleIcon ? root.ruleIcon : "slideshow") + ".svg"
anchors.centerIn: parent
}
}
}
Repeater {
model: ["torch-on", "torch-off", "alarm-clock", "media-preview-start", "network-secure", "notification", "sensors", "shutter-10", "mail-mark-important", "eye"]
delegate: Item {
Layout.fillWidth: true
Layout.preferredHeight: app.iconSize + app.margins
Rectangle {
height: parent.height
width: height
color: "transparent"
border.width: 2
border.color: modelData === root.ruleIcon ? app.guhAccent : "transparent"
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: {
root.ruleIcon = modelData
}
}
ColorIcon {
height: app.iconSize
width: app.iconSize
color: root.ruleColor
name: "../images/" + modelData + ".svg"
anchors.centerIn: parent
}
}
}
}
}
ThinDivider { visible: !root.isStateBased }
@ -277,7 +377,7 @@ Page {
Layout.margins: app.margins
font.pixelSize: app.mediumFont
wrapMode: Text.WordWrap
text: eventsRepeater.count === 0 && timeEventRepeater.count === 0 ?
text: eventsRepeater.count === 0 && timeEventRepeater.count === 0 && actionsRepeater.count === 0 ?
qsTr("Execute actions when something happens.") :
qsTr("When any of these events happen...")
visible: !root.isStateBased
@ -425,9 +525,10 @@ Page {
ThinDivider { visible: root.actionsVisible }
Label {
text: root.isStateBased ?
(root.rule.stateEvaluator === 0 ? qsTr("...come true, execute those actions:") : qsTr("...comes true, execute those actions:")) :
qsTr("...execute those actions:")
text: root.isEmpty ? qsTr("Create a scene.") :
root.isStateBased ?
(root.rule.stateEvaluator === 0 ? qsTr("...come true, execute those actions:") : qsTr("...comes true, execute those actions:")) :
qsTr("...execute those actions:")
font.pixelSize: app.mediumFont
Layout.fillWidth: true
Layout.margins: app.margins
@ -436,6 +537,17 @@ Page {
font.bold: true
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
wrapMode: Text.WordWrap
font.pixelSize: app.smallFont
font.italic: true
text: qsTr("Just pick some actions which will be executed when the scene is activated. Scenes are like any other magic except they can also be activated manually.")
visible: root.isEmpty
}
Repeater {
id: actionsRepeater
model: root.actionsVisible ? root.rule.actions : null
@ -449,8 +561,12 @@ Page {
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: actionsRepeater.count == 0 ? qsTr("Add an action...") : qsTr("Add another action...")
text: root.isEmpty ? qsTr("Configure...") :
actionsRepeater.count == 0 ? qsTr("Add an action...") : qsTr("Add another action...")
onClicked: {
if (root.isEmpty) {
root.rule.executable = true;
}
var page = pageStack.push(ruleActionQuestionPageComponent, {exitAction: false});
}
visible: root.actionsVisible

View File

@ -37,7 +37,7 @@ Page {
Component.onCompleted: {
actualModel.clear()
for (var i = 0; i < actionModel.count; i++) {
ifaceFilterModel.filterInterface = actionModel.get(i).interfaceName;
ifaceFilterModel.shownInterfaces = [actionModel.get(i).interfaceName];
if (actionModel.get(i).interfaceName === "" || ifaceFilterModel.count > 0) {
actualModel.append(actionModel.get(i))
}
@ -253,7 +253,7 @@ Page {
model: DevicesProxy {
id: lightsModel
devices: Engine.deviceManager.devices
filterInterface: "light"
shownInterfaces: ["light"]
}
delegate: CheckDelegate {
width: parent.width
@ -338,7 +338,7 @@ Page {
model: DevicesProxy {
id: notificationsModel
devices: Engine.deviceManager.devices
filterInterface: "notifications"
shownInterfaces: ["notifications"]
}
delegate: CheckDelegate {
width: parent.width

View File

@ -0,0 +1,33 @@
import QtQuick 2.8
import QtQuick.Controls 2.1
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.2
import Nymea 1.0
import "../components"
Item {
id: root
property alias count: interfacesGridView.count
property alias model: interfacesGridView.model
GridView {
id: interfacesGridView
anchors.fill: parent
anchors.margins: app.margins / 2
readonly property int minTileWidth: 180
readonly property int minTileHeight: 240
readonly property int tilesPerRow: root.width / minTileWidth
model: InterfacesModel {
id: interfacesModel
devices: Engine.deviceManager.devices
}
cellWidth: width / tilesPerRow
cellHeight: Math.max(cellWidth, minTileHeight)
delegate: DevicesPageDelegate {
width: interfacesGridView.cellWidth
height: interfacesGridView.cellHeight
}
}
}

View File

@ -0,0 +1,264 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.2
import Nymea 1.0
import "../components"
Item {
id: root
Pane {
anchors.fill: parent
anchors.margins: app.margins / 2
Material.elevation: 1
Column {
width: parent.width
anchors.centerIn: parent
anchors.verticalCenterOffset: -app.iconSize
spacing: app.margins
ColorIcon {
height: app.iconSize * 2
width: height
color: app.guhAccent
anchors.horizontalCenter: parent.horizontalCenter
name: interfaceToIcon(model.name)
}
Label {
text: interfaceToString(model.name).toUpperCase()
width: parent.width
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
}
MouseArea {
anchors.fill: parent
onClicked: {
var page;
switch (model.name) {
case "light":
page = "LightsDeviceListPage.qml"
break;
default:
page = "GenericDeviceListPage.qml"
}
if (model.name === "uncategorized") {
pageStack.push(Qt.resolvedUrl("../devicelistpages/" + page), {hiddenInterfaces: app.supportedInterfaces})
} else {
pageStack.push(Qt.resolvedUrl("../devicelistpages/" + page), {shownInterfaces: [model.name]})
}
}
}
DevicesProxy {
id: devicesProxy
devices: Engine.deviceManager.devices
shownInterfaces: [model.name]
}
}
Item {
id: inlineControlPane
anchors { left: parent.left; bottom: parent.bottom; right: parent.right; margins: app.margins / 2 }
height: app.iconSize + app.margins * 2
Rectangle {
anchors.fill: parent
// color: app.guhAccent
color: "black"
opacity: .05
}
Loader {
id: inlineControlLoader
anchors {
fill: parent
leftMargin: app.margins
rightMargin: app.margins
topMargin: app.margins / 2
bottomMargin: app.margins / 2
}
sourceComponent: {
switch (model.name) {
case "sensor":
case "weather":
return labelComponent;
case "light":
case "media":
case "garagegate":
case "shutter":
case "blind":
return buttonComponent
}
}
}
}
Component {
id: buttonComponent
MouseArea {
onClicked: {
switch (model.name) {
case "light":
if (devicesProxy.count == 1) {
var device = devicesProxy.get(0);
var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
var stateType = deviceClass.stateTypes.findByName("power")
var actionType = deviceClass.actionTypes.findByName("power")
var params = [];
var param1 = {};
param1["paramTypeId"] = actionType.paramTypes.get(0).id;
param1["value"] = !device.states.getState(stateType.id).value;
params.push(param1)
Engine.deviceManager.executeAction(device.id, actionType.id, params)
} else {
for (var i = 0; i < devicesProxy.count; i++) {
var device = devicesProxy.get(i);
var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
var actionType = deviceClass.actionTypes.findByName("power");
var params = [];
var param1 = {};
param1["paramTypeId"] = actionType.paramTypes.get(0).id;
param1["value"] = false;
params.push(param1)
Engine.deviceManager.executeAction(device.id, actionType.id, params)
}
}
break;
case "media":
var device = devicesProxy.get(0)
var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
var stateType = deviceClass.stateTypes.findByName("playbackStatus");
var state = device.states.getState(stateType.id)
var actionName
switch (state.value) {
case "PLAYING":
actionName = "pause";
break;
case "PAUSED":
actionName = "play";
break;
}
var actionTypeId = deviceClass.actionTypes.findByName(actionName).id;
print("executing", device, device.id, actionTypeId, actionName, deviceClass.actionTypes)
Engine.deviceManager.executeAction(device.id, actionTypeId)
case "garagegate":
case "shutter":
case "blind":
for (var i = 0; i < devicesProxy.count; i++) {
var device = devicesProxy.get(i);
var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
var actionType = deviceClass.actionTypes.findByName("close");
Engine.deviceManager.executeAction(device.id, actionType.id)
}
}
}
ColumnLayout {
anchors.fill: parent
Label {
id: label
Layout.fillWidth: true
text: {
switch (model.name) {
case "media":
return devicesProxy.get(0).name;
case "light":
var count = 0;
for (var i = 0; i < devicesProxy.count; i++) {
var device = devicesProxy.get(i);
var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
var stateType = deviceClass.stateTypes.findByName("power")
if (device.states.getState(stateType.id).value === true) {
count++;
}
}
return count === 0 ? qsTr("All off") : qsTr("%1 on").arg(count)
case "garagegate":
var count = 0;
for (var i = 0; i < devicesProxy.count; i++) {
var device = devicesProxy.get(i);
var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
var stateType = deviceClass.stateTypes.findByName("state");
if (device.states.getState(stateType.id).value !== "closed") {
count++;
}
}
return count === 0 ? qsTr("All closed") : qsTr("%1 open").arg(count)
case "shutter":
return qsTr("%1 installed").arg(devicesProxy.count)
}
console.warn("Unhandled interface", model.name)
}
font.pixelSize: app.smallFont
elide: Text.ElideRight
}
ColorIcon {
id: icon
width: app.largeFont
height: width
color: app.guhAccent
Layout.alignment: Qt.AlignRight
name: {
switch (model.name) {
case "media":
var device = devicesProxy.get(0)
var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
var stateType = deviceClass.stateTypes.findByName("playbackStatus");
var state = device.states.getState(stateType.id)
return state.value === "PLAYING" ? "../images/media-playback-pause.svg" :
state.value === "PAUSED" ? "../images/media-playback-start.svg" :
""
case "light":
return "../images/system-shutdown.svg"
case "garagegate":
case "shutter":
case "blind":
return "../images/down.svg"
}
}
}
}
}
}
Component {
id: labelComponent
ColumnLayout {
property var device: devicesProxy.get(0)
property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null
property var state: deviceClass ? device.states.getState(deviceClass.stateTypes.findByName("temperature").id) : null
Label {
text: parent.device.name
font.pixelSize: app.smallFont
Layout.fillWidth: true
elide: Text.ElideRight
}
Label {
font.pixelSize: app.largeFont
color: app.guhAccent
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
text: {
if (devicesProxy.count > 0) {
var stateName;
// switch (model.name) {
// case "sensor":
// }
return parent.state.value + "°C";
}
}
}
}
}
}

View File

@ -0,0 +1,146 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.2
import Nymea 1.0
import "../components"
Item {
id: root
readonly property int count: tagsProxy.count
TagsProxyModel {
id: tagsProxy
filterTagId: "favorites"
}
GridView {
id: gridView
anchors.fill: parent
anchors.margins: app.margins / 2
readonly property int minTileWidth: 180
readonly property int minTileHeight: 240
readonly property int tilesPerRow: root.width / minTileWidth
cellWidth: gridView.width / tilesPerRow
cellHeight: cellWidth
model: tagsProxy
delegate: favoritesDelegateComponent
MouseArea {
id: dndArea
anchors.fill: parent
propagateComposedEvents: true
property int from: -1
property int to: -1
property int index: gridView.indexAt(mouseX, mouseY) // Item underneath cursor
property var dndDelegate: null
property int dx
property int dy
onPressAndHold: {
//currentId = icons.get(newIndex = index).gridId
preventStealing = true;
from = index;
print("pressandHold on", index)
var tag = tagsProxy.get(index);
var originalDelegate = gridView.itemAt(mouseX, mouseY)
dndDelegate = favoritesDelegateComponent.createObject(dndArea, {deviceId: tag.deviceId, ruleId: tag.ruleId, x: originalDelegate.x, y: originalDelegate.y})
dx = mouseX - originalDelegate.x;
dy = mouseY - originalDelegate.y;
}
onReleased: {
preventStealing = false;
from = -1;
dndDelegate.destroy();
}
onPositionChanged: {
if (dndDelegate) {
dndDelegate.x = mouseX - dx
dndDelegate.y = mouseY - dy
}
if (dndArea.from >= 0 && to != index && from != index) {
to = index;
print("should move", from, "to", to)
for (var i = 0; i < tagsProxy.count; i++) {
if (i < Math.min(from, to) || i > Math.max(from, to)) {
// outside the range... don't touch
continue;
}
var newIdx;
if (i == from) {
newIdx = to;
} else {
if (from < to) {
// item is moved down the list
newIdx = i - 1;
} else {
newIdx = i + 1;
}
}
var tag = tagsProxy.get(i);
Engine.tagsManager.tagDevice(tag.deviceId, tag.tagId, newIdx);
}
from = index;
}
// tagsProxy.move(newIndex, newIndex = index, 1)
}
}
}
Component {
id: favoritesDelegateComponent
Item {
id: delegateRoot
property string deviceId: model.deviceId
property string ruleId: model.ruleId
readonly property var device: Engine.deviceManager.devices.getDevice(deviceId)
readonly property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null
visible: index !== undefined && index !== dndArea.from
width: gridView.cellWidth
height: gridView.cellHeight
Pane {
id: pane
anchors.fill: parent
anchors.margins: app.margins / 2
Material.elevation: 1
ColumnLayout {
anchors.centerIn: parent
width: parent.width - app.margins * 2
spacing: app.margins
ColorIcon {
Layout.preferredWidth: app.iconSize * 2
Layout.preferredHeight: width
name: app.interfacesToIcon(delegateRoot.deviceClass.interfaces)
color: app.guhAccent
Layout.alignment: Qt.AlignHCenter
}
Label {
Layout.fillWidth: true
text: delegateRoot.device.name
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
pageStack.push(Qt.resolvedUrl("../devicepages/" + app.interfaceListToDevicePage(delegateRoot.deviceClass.interfaces)), {device: delegateRoot.device})
}
}
}
}
}

View File

@ -0,0 +1,79 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Nymea 1.0
import QtQuick.Controls.Material 2.2
import "../components"
Item {
id: root
readonly property int count: interfacesGridView.count
GridView {
id: interfacesGridView
anchors.fill: parent
anchors.margins: app.margins / 2
readonly property int minTileWidth: 180
readonly property int minTileHeight: 180
readonly property int tilesPerRow: root.width / minTileWidth
model: RulesFilterModel {
rules: Engine.ruleManager.rules
filterExecutable: true
}
cellWidth: width / tilesPerRow
cellHeight: Math.max(cellWidth, minTileHeight)
delegate: Item {
id: scenesDelegate
width: interfacesGridView.cellWidth
height: interfacesGridView.cellHeight
property var colorTag: Engine.tagsManager.tags.findRuleTag(model.id, "color")
property var iconTag: Engine.tagsManager.tags.findRuleTag(model.id, "icon")
Connections {
target: Engine.tagsManager.tags
onCountChanged: {
colorTag = Engine.tagsManager.tags.findRuleTag(model.id, "color")
iconTag = Engine.tagsManager.tags.findRuleTag(model.id, "icon")
}
}
Pane {
anchors.fill: parent
anchors.margins: app.margins / 2
Material.elevation: 1
MouseArea {
anchors.fill: parent
onClicked: {
Engine.ruleManager.executeActions(model.id)
}
}
ColumnLayout {
width: parent.width
anchors.centerIn: parent
spacing: app.margins
ColorIcon {
Layout.preferredHeight: app.iconSize * 2
Layout.preferredWidth: height
Layout.alignment: Qt.AlignHCenter
name: scenesDelegate.iconTag ? "../images/" + scenesDelegate.iconTag.value + ".svg" : "../images/slideshow.svg";
color: scenesDelegate.colorTag ? scenesDelegate.colorTag.value : app.guhAccent;
}
Label {
Layout.fillWidth: true
text: model.name
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}
}