Commit a90a5baf authored by Leander Schulten's avatar Leander Schulten
Browse files

Show the lights in the map view. You can move the lights and change the value...

Show the lights in the map view. You can move the lights and change the value of the channel filter for every channel of a selected device.
parent 6c8e2c60
......@@ -16,6 +16,7 @@ ModelView{
descriptionInputEnabled: UserManagment.currentUser.havePermission(Permission.CHANGE_NAME)
addButton.text: "Add Device"
removeButton.text: "Remove Device"
property var placeOnMapCallback
Text{
Layout.row: 2
......@@ -68,7 +69,7 @@ ModelView{
}
TextInputField{
enabled: UserManagment.currentUser.havePermission(Permission.CHANGE_POSITION);
Layout.minimumWidth: 50
Layout.minimumWidth: 40
text:deviceModelView.currentItem.modelData.position.x
validator: IntValidator{}
onTextChanged: deviceModelView.currentItem.modelData.position.x = text.length?text:0
......@@ -80,10 +81,17 @@ ModelView{
}
TextInputField{
enabled: UserManagment.currentUser.havePermission(Permission.CHANGE_POSITION);
Layout.minimumWidth: 40
text:deviceModelView.currentItem.modelData.position.y
validator: IntValidator{}
onTextChanged: deviceModelView.currentItem.modelData.position.y = text.length?text:0
}
Button{
visible: deviceModelView.placeOnMapCallback && deviceModelView.currentItem.modelData.position.x === -1 || deviceModelView.currentItem.modelData.position.x === -1
text: "Place on map"
onClicked: deviceModelView.placeOnMapCallback(deviceModelView.currentItem.modelData)
height: implicitHeight - 15
}
}
Dialog{
......
......@@ -63,10 +63,13 @@ void Device::channelAdded(Channel *c){
DMXChannelFilter * Device::getFilterForChannel( Channel *c){
for(auto i = filter.cbegin();i!=filter.cend();++i){
if(i->first==c)
if(i->first==c){
QQmlEngine::setObjectOwnership(i->second,QQmlEngine::CppOwnership);
return i->second;
}
}
filter.emplace_back(c,new DMXChannelFilter);
QQmlEngine::setObjectOwnership(filter.back().second,QQmlEngine::CppOwnership);
return filter.back().second;
}
......
......@@ -61,8 +61,8 @@ public:
const std::vector<std::pair<Channel*,DMXChannelFilter*>> & getChannelFilter()const{return filter;}
DMXChannelFilter * getFilterForChannel( Channel * c);
DMXChannelFilter * getFilterForChannelindex(int intdex);
Q_INVOKABLE DMXChannelFilter * getFilterForChannel( Channel * c);
Q_INVOKABLE DMXChannelFilter * getFilterForChannelindex(int intdex);
signals:
void startDMXChannelChanged(unsigned int newStartDMXChannel);
......
......@@ -11,6 +11,12 @@
*/
class DMXChannelFilter : public QObject{
Q_OBJECT
Q_PROPERTY(unsigned char value READ getValue WRITE setValue NOTIFY valueChanged)
Q_PROPERTY(unsigned char minValue READ getMinValue WRITE setMinValue NOTIFY minValueChanged)
Q_PROPERTY(unsigned char maxValue READ getMaxValue WRITE setMaxValue NOTIFY maxValueChanged)
Q_PROPERTY(Operation minOperation READ getMinOperation WRITE setMinOperation NOTIFY minOperationChanged)
Q_PROPERTY(Operation maxOperation READ getMaxOperation WRITE setMaxOperation NOTIFY maxOperationChanged)
Q_PROPERTY(bool shouldOverrideValue READ shouldOverrideValue WRITE shouldOverrideValue NOTIFY shouldOverrideValueChanged)
public:
/**
* @brief The Operation enum legt die Operationen für den min und max value fest
......
......@@ -29,6 +29,71 @@ std::vector<unsigned char> oldData;
namespace Driver {
DMXQMLValue::DMXQMLValue(){
QQmlEngine::setObjectOwnership(this,QQmlEngine::CppOwnership);
}
DMXValueModel::DMXValueModel(){
QQmlEngine::setObjectOwnership(this,QQmlEngine::CppOwnership);
}
void DMXValueModel::setQMLEngineThread(QThread *qmlEngineThread){
if(this->qmlEngineThread == qmlEngineThread)
return;
this->qmlEngineThread = qmlEngineThread;
for(auto & v : values){
v->moveToThread(qmlEngineThread);
}
}
void DMXValueModel::setValues(unsigned char *v, size_t size){
if(!enableUpdates)
return;
int diffSize = static_cast<int>(size - values.size());
if(diffSize>0){
beginInsertRows(QModelIndex(),static_cast<int>(values.size()),static_cast<int>(values.size())+diffSize);
for(int i = 0; i < diffSize; ++i){
values.emplace_back(std::make_unique<DMXQMLValue>());
if(qmlEngineThread)
values.back()->moveToThread(qmlEngineThread);
}
emit valuesChanged();
}else if(diffSize<0){
beginRemoveRows(QModelIndex(),static_cast<int>(values.size()) + diffSize,static_cast<int>(values.size()) - 1);
values.resize(size);
emit valuesChanged();
}
if(diffSize>0){
endInsertRows();
}else if(diffSize<0){
endRemoveRows();
}
for(auto dmxValue = values.begin(); dmxValue != values.end();++dmxValue,++v){
**dmxValue = *v;
}
}
QHash<int, QByteArray> DMXValueModel::roleNames() const{
auto map = QAbstractListModel::roleNames();
map.insert(ValueRole,"value");
return map;
}
QVariant DMXValueModel::data(const QModelIndex &index, int role) const{
Q_UNUSED(role)
if(index.row()<0||index.row()>=rowCount())
return {};
return QVariant::fromValue(values[static_cast<std::vector<unsigned char>::size_type>(index.row())].get());
}
DMXQMLValue * DMXValueModel::value(int index){
if(static_cast<size_t>(index) >= values.size())
return nullptr;
return values[static_cast<size_t>(index)].get();
}
bool loadAndStartDriver(QString path){
if(!loadDriver(path)){
return false;
......@@ -104,6 +169,7 @@ namespace Driver {
Programm::fill(values,size,time);
Modules::DMXConsumer::fillWithDMXConsumer(values,size);
DMXChannelFilter::filterValues(values,size);
dmxValueModel.setValues(values,static_cast<size_t>(size));
std::copy(values+1, values+size,values);
#ifdef LOG_DRIVER
if(debugOutput.is_open() && !std::equal(std::begin(oldData),std::end(oldData),values)){
......
......@@ -3,8 +3,64 @@
#include "settings.h"
#include "HardwareInterface.h"
#include <QAbstractListModel>
namespace Driver {
class DMXQMLValue : public QObject{
Q_OBJECT
Q_PROPERTY(int value READ getValue NOTIFY valueChanged)
unsigned char value;
public:
DMXQMLValue();
void operator=(unsigned char v){setValue(v);}
void setValue(unsigned char v){if(v!=value){value =v; emit valueChanged();}}
int getValue()const {return value;}
signals:
void valueChanged();
};
/**
* @brief The DMXValueModel class is a QAbstractListModel wrapper around the values send from the Driver
*/
class DMXValueModel : public QAbstractListModel{
Q_OBJECT
Q_PROPERTY(bool enableUpdates MEMBER enableUpdates NOTIFY enableUpdatesChanged)
Q_PROPERTY(DMXValueModel * values READ getValues NOTIFY valuesChanged)
std::vector<std::unique_ptr<DMXQMLValue>> values;
QThread * qmlEngineThread = nullptr;
/**
* @brief enableUpdate only if enableUpdates is true the values gets updated in setValues
*/
bool enableUpdates = true;
public:
DMXValueModel();
enum{
ValueRole = Qt::UserRole+1,
};
/**
* @brief setValues sets the values of the dmx output so that the values are available in qml
* @param v the values
* @param size the length of the values array
*/
void setValues(unsigned char *v, size_t size);
void setQMLEngineThread(QThread * qmlEngineThread);
virtual QHash<int, QByteArray> roleNames() const override;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override{
Q_UNUSED(parent)
return static_cast<int>(values.size());
}
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
Q_INVOKABLE DMXQMLValue * value(int index);
DMXValueModel * getValues(){return values.empty() ? nullptr : this;}
signals:
void enableUpdatesChanged();
void valuesChanged();
};
/**
* @brief dmxValueModel a model to access the outgoing dmx values in qml
*/
inline DMXValueModel dmxValueModel;
/**
* @brief loadAndStartDriver load see: @see loadDriver() starts the loaded driver
* @param path a path to the dll, where the Funktion getDriver can be called
......
......@@ -90,6 +90,7 @@ int main(int argc, char *argv[])
QQmlApplicationEngine engine;
ControlPanel::setQmlEngine(&engine);
ProgramBlockEditor::engine = &engine;
Driver::dmxValueModel.setQMLEngineThread(engine.thread());
// normally this should be done automatically
qRegisterMetaType<QAbstractListModel*>("QAbstractListModel*");
qRegisterMetaType<PropertyInformationModel*>("PropertyInformationModel*");
......@@ -115,6 +116,7 @@ int main(int argc, char *argv[])
qRegisterMetaType<Modules::ValueType>("ValueType");
qRegisterMetaType<Modules::ProgramBlock::Status>("Status");
qRegisterMetaType<Modules::PropertiesVector*>("PropertiesVector*");
qRegisterMetaType<Driver::DMXQMLValue*>("DMXQMLValue*");
// Load Settings and ApplicationData
Settings::setLocalSettingFile(QFileInfo("settings.ini"));
......@@ -203,6 +205,8 @@ int main(int argc, char *argv[])
engine.rootContext()->setContextProperty("UserManagment",UserManagment::get());
engine.rootContext()->setContextProperty("Settings",&settings);
engine.rootContext()->setContextProperty("spotify",&spotify);
QQmlEngine::setObjectOwnership(&Driver::dmxValueModel,QQmlEngine::CppOwnership);
engine.rootContext()->setContextProperty("dmxOutputValues",&Driver::dmxValueModel);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
......@@ -222,17 +226,18 @@ int main(int argc, char *argv[])
#else
#include "test/DriverDummy.h"
/*DriverDummy driver;
DriverDummy driver;
driver.setSetValuesCallback([](unsigned char* values, int size, double time){
std::memset(values,0,size);
DMXChannelFilter::initValues(values,size);
Programm::fill(values,size,time);
Modules::DMXConsumer::fillWithDMXConsumer(values,size);
DMXChannelFilter::filterValues(values,size);
Driver::dmxValueModel.setValues(values,size);
});
driver.setWaitTime(std::chrono::seconds(5));
driver.setWaitTime(std::chrono::milliseconds(40));
driver.init();
driver.start();*/
driver.start();
#endif
QTimer timer;
......
......@@ -45,7 +45,7 @@ ApplicationWindow {
}
VerticalTabButton {
text: qsTr("Control Pane")
Component.onCompleted: tabBar.currentIndex = 4
Component.onCompleted: tabBar.setCurrentIndex(4)
}
VerticalTabButton {
text: qsTr("Map View")
......@@ -86,7 +86,13 @@ ApplicationWindow {
y:0
currentIndex: tabBar.currentIndex
orientation: "Vertical"
DeviceView{}
DeviceView{
placeOnMapCallback: function(device){
mapView.toPlacedDevice = device;
swipeView.setCurrentIndex(mapView.SwipeView.index);
}
}
DevicePrototypeView{}
......@@ -98,7 +104,9 @@ ApplicationWindow {
id:controlPane
}
BarMapView{}
BarMapView{
id: mapView
}
LoginView{}
......
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.5
import QtGraphicalEffects 1.12
import custom.licht 1.0
ColumnLayout{
id: root
anchors.margins: 5
Rectangle{
border.width: 2
border.color: "black"
property var toPlacedDevice: null
Item{
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: 9999
Flickable{
anchors.fill: parent
anchors.margins: parent.border.width
id: flickable
clip: true
contentWidth: map.boundingBox.width
contentHeight: map.boundingBox.height
rightMargin: 30
topMargin: 30
bottomMargin: 30
leftMargin: 30
ScrollBar.horizontal: ScrollBar{}
ScrollBar.vertical: ScrollBar{}
Rectangle{
id: borderRectangle
width: deviceInfo.visible ? parent.width - deviceInfo.width - deviceInfo.anchors.leftMargin - deviceInfo.anchors.rightMargin : parent.width
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
border.width: 2
border.color: "black"
Item{
x : -map.boundingBox.x
y : -map.boundingBox.y
Behavior on x {
NumberAnimation{}
enabled: map.finishedRotationAnimation
}
Behavior on y {
NumberAnimation{}
enabled: map.finishedRotationAnimation
}
MapView{
property rect boundingBox: mapToItem(parent,0,0,width,height)
property rect lastBoundingBox
property real reachRotation : 0
property bool finishedRotationAnimation : false
rotation: reachRotation
onRotationChanged: {
if(rotation === reachRotation){
finishedRotationAnimation = true;
boundingBox = mapToItem(parent,0,0,width,height);
finishedRotationAnimation = false;
}
Flickable{
anchors.fill: parent
anchors.margins: parent.border.width
id: flickable
clip: true
contentWidth: map.boundingBox.width
contentHeight: map.boundingBox.height
rightMargin: 30
topMargin: 30
bottomMargin: 30
leftMargin: 30
ScrollBar.horizontal: ScrollBar{}
ScrollBar.vertical: ScrollBar{}
Item{
x : -map.boundingBox.x
y : -map.boundingBox.y
Behavior on x {
NumberAnimation{}
enabled: map.finishedRotationAnimation
}
onBoundingBoxChanged: {
if(lastBoundingBox&&!finishedRotationAnimation){
flickable.contentX -= boundingBox.x - lastBoundingBox.x;
flickable.contentY -= boundingBox.y - lastBoundingBox.y;
}
lastBoundingBox = boundingBox
Behavior on y {
NumberAnimation{}
enabled: map.finishedRotationAnimation
}
id: map
width: implicitWidth
height: implicitHeight
onScaleChanged: boundingBox = mapToItem(parent,0,0,width,height);
Behavior on rotation {
NumberAnimation{
duration: 400
easing.type: "InOutCubic"
MapView{
property rect boundingBox: mapToItem(parent,0,0,width,height)
property rect lastBoundingBox
property real reachRotation : 0
property bool finishedRotationAnimation : false
ToolTip.visible: root.toPlacedDevice
ToolTip.text: "Click into the map to place " + (root.toPlacedDevice?root.toPlacedDevice.name:"nothing")
rotation: reachRotation
onRotationChanged: {
if(rotation === reachRotation){
finishedRotationAnimation = true;
boundingBox = mapToItem(parent,0,0,width,height);
finishedRotationAnimation = false;
}
}
onBoundingBoxChanged: {
if(lastBoundingBox&&!finishedRotationAnimation){
flickable.contentX -= boundingBox.x - lastBoundingBox.x;
flickable.contentY -= boundingBox.y - lastBoundingBox.y;
}
lastBoundingBox = boundingBox
}
id: map
width: implicitWidth
height: implicitHeight
onScaleChanged: boundingBox = mapToItem(parent,0,0,width,height);
Behavior on rotation {
NumberAnimation{
duration: 400
easing.type: "InOutCubic"
}
}
MouseArea{
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if(root.toPlacedDevice && mouse.button === Qt.LeftButton){
root.toPlacedDevice.position.x = mouse.x - 30;
root.toPlacedDevice.position.y = mouse.y - 30;
}
root.toPlacedDevice = null;
deviceInfo.device = null;
}
onWheel: {
if(wheel.modifiers & Qt.ControlModifier){
var newScale = map.scale + wheel.angleDelta.y / 120. * 0.1;
map.scale = Math.max(0.2,Math.min(newScale,3));
wheel.accepted = true;
}else{
wheel.accepted = false
}
}
}
Repeater{
model: deviceModel
Rectangle{
visible: itemData.position.x !== -1 && itemData.position.y !== -1
property int selectScale: 1+(deviceInfo.device === itemData)
width: 5 * selectScale
height: 5 * selectScale
radius: 5 * selectScale
color: "yellow"
x: itemData.position.x - 2 * selectScale + 30
y: itemData.position.y - 2 * selectScale + 30
RadialGradient{
anchors.fill: parent
anchors.margins: -10
gradient: Gradient {
GradientStop { position: 0.2; color: Qt.rgba(1.,1.,1.,itemData.prototype.channel.rowCount() > 0 ? dmxOutputValues.values ? dmxOutputValues.values.value(itemData.startDMXChannel).value/255. : 0 : 0) }
GradientStop { position: 0.5; color: Qt.rgba(1,1,1,0) }
}
z: parent.z - 1
}
MouseArea{
id: mouseArea
enabled: !root.toPlacedDevice
anchors.fill: parent
anchors.margins: -5
hoverEnabled: true
onClicked: deviceInfo.device = itemData
drag.target: parent
drag.minimumX: 0
drag.minimumY: 0
drag.maximumX: map.width - 5
drag.maximumY: map.height - 5
drag.threshold: 0
onReleased: {
// If we dont use variables the y coordinate gets resetted after setting itemData.position.x
var newX = parent.x + 2 * parent.selectScale - 30;
var newY = parent.y + 2 * parent.selectScale - 30;
itemData.position.x = newX;
itemData.position.y = newY;
}
}
ToolTip.visible: mouseArea.containsMouse
ToolTip.delay: 500
ToolTip.text: itemData.name + "\nDmx Addess: " + itemData.startDMXChannel
}
}
}
}
}
PinchHandler{
target: map
minimumScale: 0.2
maximumScale: 2
maximumRotation: map.rotation
minimumRotation: map.rotation
xAxis.enabled: false;
yAxis.enabled: false;
}
}
PinchHandler{
target: map
minimumScale: 0.2
maximumScale: 2
maximumRotation: map.rotation
minimumRotation: map.rotation
xAxis.enabled: false;
yAxis.enabled: false;
ColumnLayout{
anchors.margins: 10
anchors.left: borderRectangle.right
anchors.top: parent.top
anchors.bottom: parent.bottom
visible: width > 0
id: deviceInfo
property var device: null
width: device ? 200 : 0
Behavior on width {
NumberAnimation{
duration: 100
easing.type: "OutCubic"
}
}
Layout.fillHeight: true
Label{
text: deviceInfo.device ? deviceInfo.device.name + " (" + deviceInfo.device.prototype.name + ")" : "No Selected Device"
font.pixelSize: 20
font.bold: true
Layout.bottomMargin: 10
}
RowLayout{
Label{
text:"x:"
}
TextInputField{
Layout.minimumWidth: 50
text: deviceInfo.device ? deviceInfo.device.position.x : ""
validator: IntValidator{}
onTextChanged: if(deviceInfo.device) deviceInfo.device.position.x = text.length?text:-1
}
Label{
Layout.leftMargin: 10
text:"y:"
}
TextInputField{
text: deviceInfo.device ? deviceInfo.device.position.y : ""
validator: IntValidator{}
onTextChanged:if(deviceInfo.device) deviceInfo.device.position.y = text.length?text:-1
}
Layout.bottomMargin: 10
}
Repeater{
model: deviceInfo.device ? deviceInfo.device.prototype.channel : null
Item{
Layout.fillWidth: true
Layout.bottomMargin: -10
implicitHeight: label.implicitHeight + slider.implicitHeight + slider.anchors.topMargin
Label{
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
id: label
text: name
font.underline: true
}
Label{
width: 30
id: valueLabel
text: slider.value
anchors.left: parent.left
anchors.verticalCenter: slider.verticalCenter
}
Slider{
id: slider
anchors.top: label.bottom
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.left: valueLabel.right
anchors.topMargin: -8
value: deviceInfo.device ? deviceInfo.device.getFilterForChannelindex(index).value : 0
from: 0
to: 255
stepSize: 1
onValueChanged: deviceInfo.device.getFilterForChannelindex(index).value = value