vehicle/decoder: decoders are now directly linked to vehicles, they are no longer managed seperatly. This makes it easier to understand for users.

Dieser Commit ist enthalten in:
Reinder Feenstra 2025-09-24 18:56:05 +02:00
Ursprung 35d25c8c07
Commit 5567ba39b8
24 geänderte Dateien mit 961 neuen und 113 gelöschten Zeilen

Datei anzeigen

@ -71,6 +71,7 @@ file(GLOB SOURCES
"src/utils/*.cpp"
"src/widget/*.hpp"
"src/widget/*.cpp"
"src/widget/decoder/*.cpp"
"src/widget/list/*.hpp"
"src/widget/list/*.cpp"
"src/widget/object/*.hpp"

Datei anzeigen

@ -53,8 +53,6 @@ QWidget* createWidgetIfCustom(const ObjectPtr& object, QWidget* parent)
{
return new InterfaceListWidget(object, parent);
}
else if(classId == "decoder_list")
return new ObjectListWidget(object, parent); // todo remove
else if(classId == "controller_list")
return new ObjectListWidget(object, parent); // todo remove
else if(classId == "rail_vehicle_list")

Datei anzeigen

@ -0,0 +1,199 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "decoderfunctionsmodel.hpp"
#include <traintastic/locale/locale.hpp>
#include "../../network/connection.hpp"
#include "../../network/error.hpp"
#include "../../network/abstractproperty.hpp"
#include "../../network/object.hpp"
#include "../../network/objectvectorproperty.hpp"
#include "../../utils/enum.hpp"
namespace {
const std::array<QString, 4> columnProperty{{
QStringLiteral("number"),
QStringLiteral("function"),
QStringLiteral("name"),
QStringLiteral("type"),
}};
}
DecoderFunctionsModel::DecoderFunctionsModel(ObjectPtr object, QObject* parent)
: QAbstractTableModel(parent)
, m_object{std::move(object)}
, m_items{m_object->getObjectVectorProperty("items")}
, m_requestId{Connection::invalidRequestId}
{
connect(m_items, &ObjectVectorProperty::valueChanged, this, &DecoderFunctionsModel::itemsChanged);
itemsChanged();
}
DecoderFunctionsModel::~DecoderFunctionsModel()
{
cancelRequest();
}
const ObjectPtr& DecoderFunctionsModel::getObject(int row) const
{
assert(row >= 0 && row < static_cast<int>(m_functions.size()));
return m_functions[row];
}
Qt::ItemFlags DecoderFunctionsModel::flags(const QModelIndex& index) const
{
if(auto* p = m_functions[index.row()]->getProperty(columnProperty[index.column()]); p && p->getAttributeBool(AttributeName::Enabled, true))
{
return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
}
return QAbstractTableModel::flags(index);
}
int DecoderFunctionsModel::rowCount(const QModelIndex& /*parent*/) const
{
return static_cast<int>(m_functions.size());
}
int DecoderFunctionsModel::columnCount(const QModelIndex& /*parent*/) const
{
return 4;
}
QVariant DecoderFunctionsModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(role == Qt::DisplayRole && orientation == Qt::Horizontal)
{
switch(section)
{
case columnNumber:
return QStringLiteral("#");
case columnFunction:
return Locale::tr("decoder_function:function");
case columnName:
return Locale::tr("object:name");
case columnType:
return Locale::tr("decoder_function:type");
}
}
return {};
}
QVariant DecoderFunctionsModel::data(const QModelIndex& index, int role) const
{
if(role == Qt::DisplayRole || role == Qt::EditRole)
{
const auto& function = *m_functions[index.row()];
if(role == Qt::EditRole)
{
return function.getPropertyValue(columnProperty[index.column()]);
}
switch(index.column())
{
case columnNumber:
return QString("F%1").arg(function.getPropertyValueInt(QStringLiteral("number"), -1));
case columnFunction:
if(auto* property = function.getProperty(QStringLiteral("function"))) [[likely]]
{
return translateEnum(*property);
}
break;
case columnName:
return function.getPropertyValueString(QStringLiteral("name"));
case columnType:
if(auto* property = function.getProperty(QStringLiteral("type"))) [[likely]]
{
return translateEnum(*property);
}
break;
}
}
return {};
}
bool DecoderFunctionsModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if(role == Qt::EditRole)
{
auto& function = *m_functions[index.row()];
switch(index.column())
{
case columnNumber:
function.setPropertyValue(columnProperty[index.column()], value.toInt());
break;
default:
function.setPropertyValue(columnProperty[index.column()], value);
break;
}
return true;
}
return false;
}
void DecoderFunctionsModel::cancelRequest()
{
if(m_requestId != Connection::invalidRequestId)
{
m_items->object().connection()->cancelRequest(m_requestId);
m_requestId = Connection::invalidRequestId;
}
}
void DecoderFunctionsModel::itemsChanged()
{
if(!m_items) [[unlikely]]
{
return;
}
cancelRequest();
if(!m_items->empty())
{
m_requestId = m_items->getObjects(
[this](const std::vector<ObjectPtr>& objects, std::optional<const Error> error)
{
m_requestId = Connection::invalidRequestId;
if(!error)
{
beginResetModel();
m_functions = objects;
endResetModel();
}
});
}
else
{
beginResetModel();
m_functions.clear();
endResetModel();
}
}

Datei anzeigen

@ -0,0 +1,62 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_CLIENT_WIDGET_DECODER_DECODERFUNCTIONSMODEL_HPP
#define TRAINTASTIC_CLIENT_WIDGET_DECODER_DECODERFUNCTIONSMODEL_HPP
#include <QAbstractTableModel>
#include "../../network/objectptr.hpp"
class ObjectVectorProperty;
class DecoderFunctionsModel : public QAbstractTableModel
{
private:
ObjectPtr m_object;
ObjectVectorProperty* m_items;
std::vector<ObjectPtr> m_functions;
int m_requestId;
void cancelRequest();
void itemsChanged();
public:
static constexpr int columnNumber = 0;
static constexpr int columnFunction = 1;
static constexpr int columnName = 2;
static constexpr int columnType = 3;
explicit DecoderFunctionsModel(ObjectPtr object, QObject* parent = nullptr);
~DecoderFunctionsModel() override;
const ObjectPtr& getObject(int row) const;
Qt::ItemFlags flags(const QModelIndex& index) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
};
#endif

Datei anzeigen

@ -0,0 +1,223 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "decoderfunctionswidget.hpp"
#include <QVBoxLayout>
#include <QToolBar>
#include <QTableView>
#include <QHeaderView>
#include <QStyledItemDelegate>
#include <QComboBox>
#include "../../network/callmethod.hpp"
#include "../../network/connection.hpp"
#include "../../network/error.hpp"
#include "../../network/method.hpp"
#include "../../network/object.hpp"
#include "../../network/objectproperty.hpp"
#include "../../misc/methodaction.hpp"
#include "../../theme/theme.hpp"
#include "decoderfunctionsmodel.hpp"
#include <traintastic/enum/decoderfunctionfunction.hpp>
#include <traintastic/enum/decoderfunctiontype.hpp>
#include <traintastic/locale/locale.hpp>
namespace {
template<typename T>
class ComboBoxDelegate : public QStyledItemDelegate
{
private:
const std::span<const T> m_values;
public:
ComboBoxDelegate(std::span<const T> values, QObject *parent = nullptr)
: QStyledItemDelegate(parent)
, m_values{values}
{
}
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem&, const QModelIndex&) const override
{
QComboBox* editor = new QComboBox(parent);
for(auto value : m_values)
{
editor->addItem(Locale::tr(QString("%1:%2").arg(EnumName<T>::value).arg(EnumValues<T>::value.at(value))), static_cast<qint64>(value));
}
return editor;
}
void setEditorData(QWidget* editor, const QModelIndex& index) const override
{
QComboBox* comboBox = static_cast<QComboBox*>(editor);
const int n = comboBox->findData(index.data(Qt::EditRole));
if(n >= 0)
{
comboBox->setCurrentIndex(n);
}
}
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override
{
QComboBox* comboBox = static_cast<QComboBox*>(editor);
model->setData(index, comboBox->currentData(), Qt::EditRole);
}
};
}
DecoderFunctionsWidget::DecoderFunctionsWidget(ObjectProperty& decoderProperty, QWidget* parent)
: QWidget(parent)
, m_decoderProperty{decoderProperty}
, m_toolbar{new QToolBar(this)}
, m_table{new QTableView(this)}
, m_requestId{Connection::invalidRequestId}
{
setWindowTitle(Locale::tr("decoder:functions"));
setLayout(new QVBoxLayout());
layout()->addWidget(m_toolbar);
layout()->addWidget(m_table);
connect(&m_decoderProperty, &ObjectProperty::valueChanged, this, &DecoderFunctionsWidget::decoderChanged);
decoderChanged();
}
DecoderFunctionsWidget::~DecoderFunctionsWidget()
{
cancelRequest();
}
const ObjectPtr& DecoderFunctionsWidget::getSelectedFunction() const
{
static const ObjectPtr null;
if(m_table->selectionModel()->hasSelection())
{
const int row = m_table->selectionModel()->selection().first().indexes().front().row();
return static_cast<DecoderFunctionsModel*>(m_table->model())->getObject(row);
}
return null;
}
void DecoderFunctionsWidget::cancelRequest()
{
if(m_requestId != Connection::invalidRequestId)
{
m_decoderProperty.object().connection()->cancelRequest(m_requestId);
m_requestId = Connection::invalidRequestId;
}
}
void DecoderFunctionsWidget::decoderChanged()
{
m_table->setModel(nullptr);
cancelRequest();
if(m_decoderProperty.hasObject())
{
const auto id = m_decoderProperty.objectId() + ".functions";
m_requestId = m_decoderProperty.object().connection()->getObject(id,
[this](const ObjectPtr& object, std::optional<const Error> /*error*/)
{
m_requestId = Connection::invalidRequestId;
if(object)
{
if(auto* create = object->getMethod(QStringLiteral("create")))
{
m_toolbar->addAction(new MethodAction(Theme::getIcon("add"), *create));
}
if(auto* delete_ = object->getMethod(QStringLiteral("delete")))
{
m_delete = new MethodAction(Theme::getIcon("delete"), *delete_,
[this, delete_]()
{
if(const auto& function = getSelectedFunction())
{
callMethod(*delete_, nullptr, function);
}
});
m_toolbar->addAction(m_delete);
}
if(!m_toolbar->actions().isEmpty())
{
m_toolbar->addSeparator();
}
if(auto* moveUp = object->getMethod(QStringLiteral("move_up")))
{
m_moveUp = new MethodAction(Theme::getIcon("up"), *moveUp,
[this, moveUp]()
{
if(const auto& function = getSelectedFunction())
{
callMethod(*moveUp, nullptr, function);
}
});
m_toolbar->addAction(m_moveUp);
}
if(auto* moveDown = object->getMethod(QStringLiteral("move_down")))
{
m_moveDown = new MethodAction(Theme::getIcon("down"), *moveDown,
[this, moveDown]()
{
if(const auto& function = getSelectedFunction())
{
callMethod(*moveDown, nullptr, function);
}
});
m_toolbar->addAction(m_moveDown);
}
const int acw = m_table->fontMetrics().averageCharWidth();
m_table->setModel(new DecoderFunctionsModel(object, this));
m_table->setColumnWidth(DecoderFunctionsModel::columnNumber, 4 * acw);
m_table->setColumnWidth(DecoderFunctionsModel::columnFunction, 15 * acw);
m_table->horizontalHeader()->setSectionResizeMode(DecoderFunctionsModel::columnName, QHeaderView::Stretch);
m_table->setColumnWidth(DecoderFunctionsModel::columnType, 15 * acw);
m_table->setItemDelegateForColumn(DecoderFunctionsModel::columnFunction, new ComboBoxDelegate<DecoderFunctionFunction>(decoderFunctionFunctionValues, m_table));
m_table->setItemDelegateForColumn(DecoderFunctionsModel::columnType, new ComboBoxDelegate<DecoderFunctionType>(decoderFunctionTypeValues, m_table));
m_table->setSelectionMode(QTableView::SingleSelection);
connect(m_table->selectionModel(), &QItemSelectionModel::selectionChanged, this,
[this](const QItemSelection& selected, const QItemSelection& /*deselected*/)
{
const bool emptySelection = selected.empty();
const bool firstRowSelected = !emptySelection && selected.front().indexes().front().row() == 0;
const bool lastRowSelected = !emptySelection && selected.front().indexes().front().row() == m_table->model()->rowCount() - 1;
if(m_delete)
{
m_delete->setForceDisabled(emptySelection);
}
if(m_moveUp)
{
m_moveUp->setForceDisabled(emptySelection || firstRowSelected);
}
if(m_moveDown)
{
m_moveDown->setForceDisabled(emptySelection || lastRowSelected);
}
});
}
});
}
}

Datei anzeigen

@ -0,0 +1,55 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_CLIENT_WIDGET_DECODER_DECODERFUNCTIONSWIDGET_HPP
#define TRAINTASTIC_CLIENT_WIDGET_DECODER_DECODERFUNCTIONSWIDGET_HPP
#include <QWidget>
#include "../../network/objectptr.hpp"
class QToolBar;
class QTableView;
class ObjectProperty;
class MethodAction;
class DecoderFunctionsWidget : public QWidget
{
private:
ObjectProperty& m_decoderProperty;
ObjectPtr m_decoder;
QToolBar* m_toolbar;
MethodAction* m_delete = nullptr;
MethodAction* m_moveUp = nullptr;
MethodAction* m_moveDown = nullptr;
QTableView* m_table;
int m_requestId;
const ObjectPtr& getSelectedFunction() const;
void cancelRequest();
void decoderChanged();
public:
explicit DecoderFunctionsWidget(ObjectProperty& decoderProperty, QWidget* parent = nullptr);
~DecoderFunctionsWidget() override;
};
#endif

Datei anzeigen

@ -0,0 +1,130 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "decoderwidget.hpp"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPushButton>
#include <QFormLayout>
#include "../../network/connection.hpp"
#include "../../network/error.hpp"
#include "../../network/method.hpp"
#include "../../network/object.hpp"
#include "../../network/objectproperty.hpp"
#include "../../network/property.hpp"
#include "../object/objecteditwidget.hpp"
#include "../interfaceitemnamelabel.hpp"
#include "../objectpropertycombobox.hpp"
#include "../createwidget.hpp"
DecoderWidget::DecoderWidget(ObjectProperty& decoderProperty, QWidget* parent)
: QWidget(parent)
, m_decoderProperty{decoderProperty}
, m_createDecoder{m_decoderProperty.object().getMethod("create_decoder")}
, m_requestId{Connection::invalidRequestId}
{
setWindowTitle(m_decoderProperty.displayName());
connect(&m_decoderProperty, &ObjectProperty::valueChanged, this, &DecoderWidget::decoderChanged);
decoderChanged();
}
DecoderWidget::~DecoderWidget()
{
cancelRequest();
}
void DecoderWidget::cancelRequest()
{
if(m_requestId != Connection::invalidRequestId)
{
m_decoderProperty.object().connection()->cancelRequest(m_requestId);
m_requestId = Connection::invalidRequestId;
}
}
void DecoderWidget::decoderChanged()
{
delete layout();
delete m_createDecoderButton;
m_createDecoderButton = nullptr;
m_object.reset();
cancelRequest();
if(m_decoderProperty.hasObject())
{
m_requestId = m_decoderProperty.getObject(
[this](const ObjectPtr& object, std::optional<const Error> /*error*/)
{
m_requestId = Connection::invalidRequestId;
if(object)
{
m_object = object;
auto* form = new QFormLayout();
if(auto* interface = dynamic_cast<ObjectProperty*>(m_object->getProperty("interface")))
{
form->addRow(new InterfaceItemNameLabel(*interface, this), new ObjectPropertyComboBox(*interface, this));
}
if(auto* protocol = dynamic_cast<Property*>(m_object->getProperty("protocol")))
{
form->addRow(new InterfaceItemNameLabel(*protocol, this), createWidget(*protocol, this));
}
if(auto* address = dynamic_cast<Property*>(m_object->getProperty("address")))
{
form->addRow(new InterfaceItemNameLabel(*address, this), createWidget(*address, this));
}
if(auto* speedSteps = dynamic_cast<Property*>(m_object->getProperty("speed_steps")))
{
form->addRow(new InterfaceItemNameLabel(*speedSteps, this), createWidget(*speedSteps, this));
}
setLayout(form);
}
});
}
else
{
m_createDecoderButton = new QPushButton("Add decoder", this);
m_createDecoderButton->setEnabled(m_createDecoder);
connect(m_createDecoderButton, &QPushButton::clicked,
[this]()
{
if(m_createDecoder) [[likely]]
{
m_createDecoder->call();
}
});
auto* h = new QHBoxLayout();
h->addStretch();
h->addWidget(m_createDecoderButton);
h->addStretch();
auto* v = new QVBoxLayout();
v->addStretch();
v->addLayout(h);
v->addStretch();
setLayout(v);
}
}

Datei anzeigen

@ -0,0 +1,50 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_CLIENT_WIDGET_DECODER_DECODERWIDGET_HPP
#define TRAINTASTIC_CLIENT_WIDGET_DECODER_DECODERWIDGET_HPP
#include <QWidget>
#include "../../network/objectptr.hpp"
class QPushButton;
class ObjectProperty;
class Method;
class ObjectEditWidget;
class DecoderWidget : public QWidget
{
private:
ObjectProperty& m_decoderProperty;
Method* m_createDecoder;
QPushButton* m_createDecoderButton = nullptr;
ObjectPtr m_object;
int m_requestId;
void cancelRequest();
void decoderChanged();
public:
explicit DecoderWidget(ObjectProperty& decoderProperty, QWidget* parent = nullptr);
~DecoderWidget() override;
};
#endif

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020,2023-2024 Reinder Feenstra
* Copyright (C) 2019-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -43,6 +43,8 @@
#include "../unitpropertycombobox.hpp"
#include "../unitpropertyedit.hpp"
#include "../createwidget.hpp"
#include "../decoder/decoderwidget.hpp"
#include "../decoder/decoderfunctionswidget.hpp"
#include "../../theme/theme.hpp"
#include <traintastic/enum/direction.hpp>
#include <traintastic/locale/locale.hpp>
@ -94,7 +96,13 @@ void ObjectEditWidget::buildForm()
if(baseProperty->type() == ValueType::Object)
{
ObjectProperty* property = static_cast<ObjectProperty*>(baseProperty);
if(contains(baseProperty->flags(), PropertyFlags::SubObject))
if(property->name() == "decoder")
{
tabs.append(new DecoderWidget(*property, this));
tabs.append(new DecoderFunctionsWidget(*property, this));
continue;
}
else if(contains(baseProperty->flags(), PropertyFlags::SubObject))
{
tabs.append(new ObjectEditWidget(*property, this));
continue;

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2024 Reinder Feenstra
* Copyright (C) 2019-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -484,7 +484,7 @@ void ObjectListWidget::tableSelectionChanged(bool hasSelection)
bool ObjectListWidget::hasEdit() const
{
if(object()->classId() == "list.output")
if(object()->classId() == "list.decoder" || object()->classId() == "list.output")
{
return false;
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2023,2025 Reinder Feenstra
* Copyright (C) 2019-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -39,11 +39,15 @@
CREATE_IMPL(Decoder)
std::shared_ptr<Decoder> Decoder::create(World& world)
{
return create(world, world.getUniqueId(defaultId));
}
const std::shared_ptr<Decoder> Decoder::null;
Decoder::Decoder(World& world, std::string_view _id) :
IdObject(world, _id),
name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store},
interface{this, "interface", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store,
[this](const std::shared_ptr<DecoderController>& value)
{
@ -110,15 +114,10 @@ Decoder::Decoder(World& world, std::string_view _id) :
changed(DecoderChangeFlags::Throttle);
updateEditable();
}},
functions{this, "functions", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject},
notes{this, "notes", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
functions{this, "functions", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
{
functions.setValueInternal(std::make_shared<DecoderFunctions>(*this, functions.name()));
Attributes::addDisplayName(name, DisplayName::Object::name);
Attributes::addEnabled(name, false);
m_interfaceItems.add(name);
Attributes::addDisplayName(interface, DisplayName::Hardware::interface);
Attributes::addEnabled(interface, false);
Attributes::addObjectList(interface, m_world.decoderControllers);
@ -162,8 +161,6 @@ Decoder::Decoder(World& world, std::string_view _id) :
Attributes::addObjectEditor(throttle, false);
m_interfaceItems.add(throttle);
m_interfaceItems.add(functions);
Attributes::addDisplayName(notes, DisplayName::Object::notes);
m_interfaceItems.add(notes);
updateEditable();
updateMute();
@ -361,6 +358,11 @@ void Decoder::destroying()
m_driver->release();
assert(!m_driver);
}
if(vehicle)
{
assert(vehicle->decoder.value().get() == this);
vehicle->decoder.setValueInternal(nullptr);
}
if(interface.value())
interface = nullptr;
m_world.decoders->removeObject(shared_ptr<Decoder>());
@ -473,7 +475,6 @@ void Decoder::updateEditable()
void Decoder::updateEditable(bool editable)
{
const bool stopped = editable && almostZero(throttle.value());
Attributes::setEnabled(name, editable);
Attributes::setEnabled(interface, stopped);
Attributes::setEnabled(protocol, stopped && protocol.getSpanAttribute<DecoderProtocol>(AttributeName::Values).length() > 1);
Attributes::setEnabled(address, stopped);

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2023,2025 Reinder Feenstra
* Copyright (C) 2019-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -77,7 +77,9 @@ class Decoder : public IdObject
public:
CLASS_ID("decoder")
DEFAULT_ID("decoder")
CREATE_DEF(Decoder)
static std::shared_ptr<Decoder> create(World& world);
static constexpr uint8_t speedStepsAuto = 0;
static constexpr float throttleMin = 0;
@ -101,7 +103,6 @@ class Decoder : public IdObject
static const std::shared_ptr<Decoder> null;
Property<std::string> name;
ObjectProperty<DecoderController> interface;
Property<DecoderProtocol> protocol;
Property<uint16_t> address;
@ -113,7 +114,6 @@ class Decoder : public IdObject
ObjectProperty<RailVehicle> vehicle;
Property<float> throttle;
ObjectProperty<DecoderFunctions> functions;
Property<std::string> notes;
boost::signals2::signal<void (Decoder&, DecoderChangeFlags, uint32_t)> decoderChanged;

Datei anzeigen

@ -26,7 +26,7 @@
#include "../../core/object.hpp"
#include "../../core/property.hpp"
#include "../../enum/decoderfunctiontype.hpp"
#include "../../enum/decoderfunctionfunction.hpp"
#include <traintastic/enum/decoderfunctionfunction.hpp>
class Decoder;

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2023 Reinder Feenstra
* Copyright (C) 2019-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -31,30 +31,8 @@
DecoderList::DecoderList(Object& _parent, std::string_view parentPropertyName, DecoderListColumn _columns) :
ObjectList<Decoder>(_parent, parentPropertyName),
columns{_columns},
create{*this, "create",
[this]()
{
auto& world = getWorld(parent());
auto decoder = Decoder::create(world, world.getUniqueId("decoder"));
if(const auto controller = std::dynamic_pointer_cast<DecoderController>(parent().shared_from_this()))
{
// todo: select free address?
decoder->interface = controller;
}
return decoder;
}}
, delete_{*this, "delete", std::bind(&DecoderList::deleteMethodHandler, this, std::placeholders::_1)}
columns{_columns}
{
const bool editable = contains(getWorld(parent()).state.value(), WorldState::Edit);
Attributes::addDisplayName(create, DisplayName::List::create);
Attributes::addEnabled(create, editable);
m_interfaceItems.add(create);
Attributes::addDisplayName(delete_, DisplayName::List::remove);
Attributes::addEnabled(delete_, editable);
m_interfaceItems.add(delete_);
}
TableModelPtr DecoderList::getModel()
@ -88,16 +66,6 @@ std::shared_ptr<Decoder> DecoderList::getDecoder(DecoderProtocol protocol, uint1
return {};
}
void DecoderList::worldEvent(WorldState state, WorldEvent event)
{
ObjectList<Decoder>::worldEvent(state, event);
const bool editable = contains(state, WorldState::Edit);
Attributes::setEnabled(create, editable);
Attributes::setEnabled(delete_, editable);
}
bool DecoderList::isListedProperty(std::string_view name)
{
return DecoderListTableModel::isListedProperty(name);

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2023 Reinder Feenstra
* Copyright (C) 2019-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -29,18 +29,14 @@
class DecoderList : public ObjectList<Decoder>
{
CLASS_ID("decoder_list")
CLASS_ID("list.decoder")
protected:
void worldEvent(WorldState state, WorldEvent event) final;
bool isListedProperty(std::string_view name) final;
public:
const DecoderListColumn columns;
Method<std::shared_ptr<Decoder>()> create;
Method<void(const std::shared_ptr<Decoder>&)> delete_;
DecoderList(Object& _parent, std::string_view parentPropertyName, DecoderListColumn _columns);
TableModelPtr getModel() final;

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2022 Reinder Feenstra
* Copyright (C) 2019-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -23,6 +23,7 @@
#include "decoderlisttablemodel.hpp"
#include "decoderlist.hpp"
#include "../../../core/objectproperty.tpp"
#include "../../../vehicle/rail/railvehicle.hpp"
#include "../../../utils/displayname.hpp"
static std::string_view displayName(DecoderListColumn column)
@ -88,7 +89,11 @@ std::string DecoderListTableModel::getText(uint32_t column, uint32_t row) const
return decoder.id;
case DecoderListColumn::Name:
return decoder.name;
if(decoder.vehicle) [[likely]]
{
return decoder.vehicle->name;
}
return {};
case DecoderListColumn::Interface:
if(const auto& interface = std::dynamic_pointer_cast<Object>(decoder.interface.value()))

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 Reinder Feenstra
* Copyright (C) 2023-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -29,6 +29,10 @@
#include "../../../core/attributes.hpp"
#include "../../../core/method.tpp"
#include "../../../core/objectproperty.tpp"
#include "../../../vehicle/rail/railvehiclelist.hpp"
#include "../../../vehicle/rail/railvehicle.hpp"
#include "../../../vehicle/rail/locomotive.hpp"
#include "../../../train/train.hpp"
#include "../../../world/getworld.hpp"
#include "../../../world/world.hpp"
@ -145,7 +149,7 @@ void MarklinCANLocomotiveList::import(const MarklinCAN::LocomotiveList::Locomoti
auto it = std::find_if(decoders.begin(), decoders.end(),
[&locomotive](const auto& item)
{
return item->name.value() == locomotive.name;
return item->vehicle && item->vehicle->name.value() == locomotive.name;
});
if(it != decoders.end())
@ -167,11 +171,15 @@ void MarklinCANLocomotiveList::import(const MarklinCAN::LocomotiveList::Locomoti
if(!decoder) // not found, create a new one
{
decoder = decoders.create();
auto vehicle = interface().world().railVehicles->create(Locomotive::classId);
decoder = vehicle->decoder.value();
}
// update it:
decoder->name = locomotive.name;
if(decoder->vehicle)
{
decoder->vehicle->name = locomotive.name;
}
decoder->protocol = locomotive.protocol;
if(decoder->protocol == DecoderProtocol::MFX)
{

Datei anzeigen

@ -24,34 +24,21 @@
#include "railvehiclelist.hpp"
#include "railvehiclelisttablemodel.hpp"
#include "../../hardware/decoder/decoder.hpp"
#include "../../hardware/decoder/list/decoderlist.hpp"
#include "../../world/world.hpp"
#include "../../core/attributes.hpp"
#include "../../core/objectproperty.tpp"
#include "../../core/objectvectorproperty.tpp"
#include "../../core/method.tpp"
#include "../../core/controllerlist.hpp"
#include "../../utils/displayname.hpp"
#include "../../train/train.hpp"
#include "../../train/trainvehiclelist.hpp"
#include "../../hardware/decoder/decodercontroller.hpp"
RailVehicle::RailVehicle(World& world, std::string_view _id) :
Vehicle(world, _id),
decoder{this, "decoder", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr,
[this](const std::shared_ptr<Decoder>& value)
{
if(decoder)
{
decoder->vehicle.setValueInternal(nullptr);
}
if(value)
{
if(value->vehicle)
{
value->vehicle->decoder = nullptr;
}
value->vehicle.setValueInternal(shared_ptr<RailVehicle>());
}
return true;
}},
decoder{this, "decoder", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store},
lob{*this, "lob", 0, LengthUnit::MilliMeter, PropertyFlags::ReadWrite | PropertyFlags::Store},
speedMax{*this, "speed_max", 0, SpeedUnit::KiloMeterPerHour, PropertyFlags::ReadWrite | PropertyFlags::Store},
weight{*this, "weight", 0, WeightUnit::Ton, PropertyFlags::ReadWrite | PropertyFlags::Store, [this](double /*value*/, WeightUnit /*unit*/){ updateTotalWeight(); }},
@ -60,6 +47,28 @@ RailVehicle::RailVehicle(World& world, std::string_view _id) :
, noSmoke{this, "no_smoke", false, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, activeTrain{this, "active_train", nullptr, PropertyFlags::ReadOnly | PropertyFlags::ScriptReadOnly | PropertyFlags::StoreState}
, trains{*this, "trains", {}, PropertyFlags::ReadOnly | PropertyFlags::ScriptReadOnly | PropertyFlags::NoStore}
, createDecoder{*this, "create_decoder", MethodFlags::NoScript,
[this]()
{
if(!decoder)
{
decoder.setValueInternal(Decoder::create(m_world));
decoder->vehicle.setValueInternal(shared_ptr<RailVehicle>());
if(m_world.decoderControllers->length == 1)
{
decoder->interface = std::dynamic_pointer_cast<DecoderController>(m_world.decoderControllers->getObject(0));
}
}
}}
, deleteDecoder{*this, "delete_decoder", MethodFlags::NoScript,
[this]()
{
if(decoder)
{
decoder->destroy();
assert(!decoder);
}
}}
{
const bool editable = contains(m_world.state.value(), WorldState::Edit);
@ -96,6 +105,12 @@ RailVehicle::RailVehicle(World& world, std::string_view _id) :
Attributes::addObjectEditor(trains, false);
m_interfaceItems.insertBefore(trains, notes);
Attributes::addObjectEditor(createDecoder, false);
m_interfaceItems.add(createDecoder);
Attributes::addObjectEditor(deleteDecoder, false);
m_interfaceItems.add(deleteDecoder);
}
void RailVehicle::setActiveTrain(const std::shared_ptr<Train>& train)
@ -149,7 +164,10 @@ void RailVehicle::destroying()
{
auto self = shared_ptr<RailVehicle>();
if(decoder)
decoder = nullptr;
{
decoder->destroy();
assert(!decoder);
}
for(const auto& train : trains)
{
train->vehicles->remove(self);

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021,2023-2024 Reinder Feenstra
* Copyright (C) 2019-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -29,6 +29,7 @@
#include "../../core/lengthproperty.hpp"
#include "../../core/speedproperty.hpp"
#include "../../core/weightproperty.hpp"
#include "../../core/method.hpp"
class Decoder;
class Train;
@ -58,6 +59,9 @@ class RailVehicle : public Vehicle
ObjectProperty<Train> activeTrain;
ObjectVectorProperty<Train> trains;
Method<void()> createDecoder;
Method<void()> deleteDecoder;
void setActiveTrain(const std::shared_ptr<Train>& train);
void updateMute();

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021,2023-2024 Reinder Feenstra
* Copyright (C) 2019-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -23,6 +23,7 @@
#include "railvehiclelist.hpp"
#include "railvehiclelisttablemodel.hpp"
#include "railvehicles.hpp"
#include "poweredrailvehicle.hpp"
#include "../../world/getworld.hpp"
#include "../../core/attributes.hpp"
#include "../../core/objectproperty.tpp"
@ -38,7 +39,12 @@ RailVehicleList::RailVehicleList(Object& _parent, std::string_view parentPropert
[this](std::string_view railVehicleClassId)
{
auto& world = getWorld(parent());
return RailVehicles::create(world, railVehicleClassId, world.getUniqueId("vehicle"));
auto vehicle = RailVehicles::create(world, railVehicleClassId, world.getUniqueId("vehicle"));
if(dynamic_cast<PoweredRailVehicle*>(vehicle.get()))
{
vehicle->createDecoder();
}
return vehicle;
}}
, delete_{*this, "delete",
[this](const std::shared_ptr<RailVehicle>& railVehicle)

Datei anzeigen

@ -454,7 +454,6 @@ World::World(Private /*unused*/) :
World::~World()
{
deleteAll(*interfaces);
deleteAll(*decoders);
deleteAll(*inputs);
deleteAll(*identifications);
deleteAll(*boards);

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic test suite.
*
* Copyright (C) 2023-2024 Reinder Feenstra
* Copyright (C) 2023-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -31,6 +31,9 @@
#include "../src/hardware/output/list/outputlist.hpp"
#include "../src/hardware/identification/identification.hpp"
#include "../src/hardware/identification/list/identificationlist.hpp"
#include "../src/vehicle/rail/locomotive.hpp"
#include "../src/vehicle/rail/railvehiclelist.hpp"
#include "../src/train/train.hpp"
#include "interfaces.hpp"
TEMPLATE_TEST_CASE("Assign decoder to another interface", "[interface]", INTERFACES_DECODER)
@ -53,7 +56,19 @@ TEMPLATE_TEST_CASE("Assign decoder to another interface", "[interface]", INTERFA
REQUIRE(worldWeak.lock()->decoders->length == 0);
REQUIRE(interfaceWeak2.lock()->decoders->length == 0);
std::weak_ptr<Decoder> decoderWeak = interfaceWeak1.lock()->decoders->create();
std::weak_ptr<Locomotive> locomotiveWeak = std::dynamic_pointer_cast<Locomotive>(world->railVehicles->create(Locomotive::classId));
REQUIRE_FALSE(locomotiveWeak.expired());
REQUIRE(locomotiveWeak.lock()->decoder);
REQUIRE_FALSE(locomotiveWeak.lock()->decoder->interface);
std::weak_ptr<Decoder> decoderWeak = locomotiveWeak.lock()->decoder.value();
REQUIRE_FALSE(decoderWeak.expired());
REQUIRE(worldWeak.lock()->interfaces->length == 2);
REQUIRE(worldWeak.lock()->decoders->length == 1);
REQUIRE(interfaceWeak1.lock()->decoders->length == 0);
REQUIRE(interfaceWeak2.lock()->decoders->length == 0);
decoderWeak.lock()->interface = interfaceWeak1.lock();
REQUIRE_FALSE(decoderWeak.expired());
REQUIRE(decoderWeak.lock()->interface.value() == std::dynamic_pointer_cast<DecoderController>(interfaceWeak1.lock()));
REQUIRE(worldWeak.lock()->interfaces->length == 2);
@ -73,6 +88,7 @@ TEMPLATE_TEST_CASE("Assign decoder to another interface", "[interface]", INTERFA
REQUIRE(worldWeak.expired());
REQUIRE(interfaceWeak1.expired());
REQUIRE(interfaceWeak2.expired());
REQUIRE(locomotiveWeak.expired());
REQUIRE(decoderWeak.expired());
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic test suite.
*
* Copyright (C) 2021-2024 Reinder Feenstra
* Copyright (C) 2021-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -122,48 +122,90 @@ TEMPLATE_TEST_CASE("Create world and interface => destroy interface", "[object-c
REQUIRE(worldWeak.expired());
}
TEST_CASE("Create world and decoder => destroy world", "[object-create-destroy]")
TEST_CASE("Create world, locomotive and decoder => destroy world", "[object-create-destroy]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
std::weak_ptr<Decoder> decoderWeak = world->decoders->create();
std::weak_ptr<Locomotive> locomotiveWeak = std::static_pointer_cast<Locomotive>(world->railVehicles->create(Locomotive::classId));
REQUIRE_FALSE(locomotiveWeak.expired());
REQUIRE(locomotiveWeak.lock()->getClassId() == Locomotive::classId);
std::weak_ptr<Decoder> decoderWeak = locomotiveWeak.lock()->decoder.value();
REQUIRE_FALSE(decoderWeak.expired());
REQUIRE(decoderWeak.lock()->getClassId() == Decoder::classId);
world.reset();
REQUIRE(decoderWeak.expired());
REQUIRE(locomotiveWeak.expired());
REQUIRE(worldWeak.expired());
}
TEST_CASE("Create world and decoder => destroy decoder", "[object-create-destroy]")
TEST_CASE("Create world, locomotive and decoder => destroy locomotive", "[object-create-destroy]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
REQUIRE(worldWeak.lock()->decoders->length == 0);
std::weak_ptr<Decoder> decoderWeak = world->decoders->create();
REQUIRE_FALSE(decoderWeak.expired());
REQUIRE(worldWeak.lock()->decoders->length == 1);
std::weak_ptr<Locomotive> locomotiveWeak = std::static_pointer_cast<Locomotive>(world->railVehicles->create(Locomotive::classId));
REQUIRE_FALSE(locomotiveWeak.expired());
REQUIRE(world->railVehicles->length == 1);
world->decoders->delete_(decoderWeak.lock());
std::weak_ptr<Decoder> decoderWeak = locomotiveWeak.lock()->decoder.value();
REQUIRE_FALSE(decoderWeak.expired());
REQUIRE(world->decoders->length == 1);
world->railVehicles->delete_(locomotiveWeak.lock());
REQUIRE(locomotiveWeak.expired());
REQUIRE(decoderWeak.expired());
REQUIRE(worldWeak.lock()->decoders->length == 0);
REQUIRE(world->railVehicles->length == 0);
REQUIRE(world->decoders->length == 0);
world.reset();
REQUIRE(worldWeak.expired());
}
TEST_CASE("Create world, decoder and function => destroy world", "[object-create-destroy]")
TEST_CASE("Create world, locomotive and decoder => delete decoder", "[object-create-destroy]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
REQUIRE(worldWeak.lock()->decoders->length == 0);
std::weak_ptr<Decoder> decoderWeak = world->decoders->create();
std::weak_ptr<Locomotive> locomotiveWeak = std::static_pointer_cast<Locomotive>(world->railVehicles->create(Locomotive::classId));
REQUIRE_FALSE(locomotiveWeak.expired());
REQUIRE(world->railVehicles->length == 1);
std::weak_ptr<Decoder> decoderWeak = locomotiveWeak.lock()->decoder.value();
REQUIRE_FALSE(decoderWeak.expired());
REQUIRE(world->decoders->length == 1);
locomotiveWeak.lock()->deleteDecoder();
REQUIRE_FALSE(locomotiveWeak.expired());
REQUIRE(decoderWeak.expired());
REQUIRE_FALSE(locomotiveWeak.lock()->decoder);
REQUIRE(world->railVehicles->length == 1);
REQUIRE(world->decoders->length == 0);
world.reset();
REQUIRE(locomotiveWeak.expired());
REQUIRE(worldWeak.expired());
}
TEST_CASE("Create world, locomotive, decoder and function => destroy world", "[object-create-destroy]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
REQUIRE(worldWeak.lock()->decoders->length == 0);
std::weak_ptr<Locomotive> locomotiveWeak = std::static_pointer_cast<Locomotive>(world->railVehicles->create(Locomotive::classId));
REQUIRE_FALSE(locomotiveWeak.expired());
REQUIRE(world->railVehicles->length == 1);
std::weak_ptr<Decoder> decoderWeak = locomotiveWeak.lock()->decoder.value();
REQUIRE_FALSE(decoderWeak.expired());
REQUIRE(worldWeak.lock()->decoders->length == 1);
@ -181,17 +223,22 @@ TEST_CASE("Create world, decoder and function => destroy world", "[object-create
REQUIRE(functionWeak.expired());
REQUIRE(functionsWeak.expired());
REQUIRE(decoderWeak.expired());
REQUIRE(locomotiveWeak.expired());
REQUIRE(worldWeak.expired());
}
TEST_CASE("Create world, decoder and function => destroy function", "[object-create-destroy]")
TEST_CASE("Create world, locomotive, decoder and function => destroy function", "[object-create-destroy]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
REQUIRE(worldWeak.lock()->decoders->length == 0);
std::weak_ptr<Decoder> decoderWeak = world->decoders->create();
std::weak_ptr<Locomotive> locomotiveWeak = std::static_pointer_cast<Locomotive>(world->railVehicles->create(Locomotive::classId));
REQUIRE_FALSE(locomotiveWeak.expired());
REQUIRE(world->railVehicles->length == 1);
std::weak_ptr<Decoder> decoderWeak = locomotiveWeak.lock()->decoder.value();
REQUIRE_FALSE(decoderWeak.expired());
REQUIRE(worldWeak.lock()->decoders->length == 1);
@ -210,10 +257,11 @@ TEST_CASE("Create world, decoder and function => destroy function", "[object-cre
world.reset();
REQUIRE(functionsWeak.expired());
REQUIRE(decoderWeak.expired());
REQUIRE(locomotiveWeak.expired());
REQUIRE(worldWeak.expired());
}
TEMPLATE_TEST_CASE("Create world, interface and decoder => destroy interface", "[object-create-destroy]", INTERFACES_DECODER)
TEMPLATE_TEST_CASE("Create world, interface, locomotive and decoder => destroy interface", "[object-create-destroy]", INTERFACES_DECODER)
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
@ -227,7 +275,11 @@ TEMPLATE_TEST_CASE("Create world, interface and decoder => destroy interface", "
REQUIRE(worldWeak.lock()->decoders->length == 0);
REQUIRE(interfaceWeak.lock()->decoders->length == 0);
std::weak_ptr<Decoder> decoderWeak = interfaceWeak.lock()->decoders->create();
std::weak_ptr<Locomotive> locomotiveWeak = std::static_pointer_cast<Locomotive>(world->railVehicles->create(Locomotive::classId));
REQUIRE_FALSE(locomotiveWeak.expired());
REQUIRE(world->railVehicles->length == 1);
std::weak_ptr<Decoder> decoderWeak = locomotiveWeak.lock()->decoder.value();
REQUIRE_FALSE(decoderWeak.expired());
REQUIRE(decoderWeak.lock()->interface.value() == std::dynamic_pointer_cast<DecoderController>(interfaceWeak.lock()));
REQUIRE(worldWeak.lock()->interfaces->length == 1);
@ -243,10 +295,11 @@ TEMPLATE_TEST_CASE("Create world, interface and decoder => destroy interface", "
world.reset();
REQUIRE(decoderWeak.expired());
REQUIRE(locomotiveWeak.expired());
REQUIRE(worldWeak.expired());
}
TEMPLATE_TEST_CASE("Create world, interface and decoder => destroy decoder", "[object-create-destroy]", INTERFACES_DECODER)
TEMPLATE_TEST_CASE("Create world, interface, locomotive and decoder => destroy locomotive", "[object-create-destroy]", INTERFACES_DECODER)
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
@ -260,14 +313,58 @@ TEMPLATE_TEST_CASE("Create world, interface and decoder => destroy decoder", "[o
REQUIRE(worldWeak.lock()->decoders->length == 0);
REQUIRE(interfaceWeak.lock()->decoders->length == 0);
std::weak_ptr<Decoder> decoderWeak = interfaceWeak.lock()->decoders->create();
std::weak_ptr<Locomotive> locomotiveWeak = std::static_pointer_cast<Locomotive>(world->railVehicles->create(Locomotive::classId));
REQUIRE_FALSE(locomotiveWeak.expired());
REQUIRE(world->railVehicles->length == 1);
std::weak_ptr<Decoder> decoderWeak = locomotiveWeak.lock()->decoder.value();
REQUIRE_FALSE(decoderWeak.expired());
REQUIRE(decoderWeak.lock()->interface.value() == std::dynamic_pointer_cast<DecoderController>(interfaceWeak.lock()));
REQUIRE(worldWeak.lock()->interfaces->length == 1);
REQUIRE(worldWeak.lock()->decoders->length == 1);
REQUIRE(interfaceWeak.lock()->decoders->length == 1);
world->decoders->delete_(decoderWeak.lock());
world->railVehicles->delete_(locomotiveWeak.lock());
REQUIRE_FALSE(interfaceWeak.expired());
REQUIRE(locomotiveWeak.expired());
REQUIRE(decoderWeak.expired());
REQUIRE(worldWeak.lock()->interfaces->length == 1);
REQUIRE(worldWeak.lock()->railVehicles->length == 0);
REQUIRE(worldWeak.lock()->decoders->length == 0);
REQUIRE(interfaceWeak.lock()->decoders->length == 0);
world.reset();
REQUIRE(interfaceWeak.expired());
REQUIRE(worldWeak.expired());
}
TEMPLATE_TEST_CASE("Create world, interface, locomotive and decoder => delete decoder", "[object-create-destroy]", INTERFACES_DECODER)
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
REQUIRE(worldWeak.lock()->interfaces->length == 0);
REQUIRE(worldWeak.lock()->decoders->length == 0);
std::weak_ptr<TestType> interfaceWeak = std::dynamic_pointer_cast<TestType>(world->interfaces->create(TestType::classId));
REQUIRE_FALSE(interfaceWeak.expired());
REQUIRE(worldWeak.lock()->interfaces->length == 1);
REQUIRE(worldWeak.lock()->decoders->length == 0);
REQUIRE(interfaceWeak.lock()->decoders->length == 0);
std::weak_ptr<Locomotive> locomotiveWeak = std::static_pointer_cast<Locomotive>(world->railVehicles->create(Locomotive::classId));
REQUIRE_FALSE(locomotiveWeak.expired());
REQUIRE(world->railVehicles->length == 1);
std::weak_ptr<Decoder> decoderWeak = locomotiveWeak.lock()->decoder.value();
REQUIRE_FALSE(decoderWeak.expired());
REQUIRE(decoderWeak.lock()->interface.value() == std::dynamic_pointer_cast<DecoderController>(interfaceWeak.lock()));
REQUIRE(worldWeak.lock()->interfaces->length == 1);
REQUIRE(worldWeak.lock()->decoders->length == 1);
REQUIRE(interfaceWeak.lock()->decoders->length == 1);
locomotiveWeak.lock()->deleteDecoder();
REQUIRE_FALSE(locomotiveWeak.lock()->decoder.value());
REQUIRE_FALSE(interfaceWeak.expired());
REQUIRE(decoderWeak.expired());
REQUIRE(worldWeak.lock()->interfaces->length == 1);
@ -275,6 +372,7 @@ TEMPLATE_TEST_CASE("Create world, interface and decoder => destroy decoder", "[o
REQUIRE(interfaceWeak.lock()->decoders->length == 0);
world.reset();
REQUIRE(locomotiveWeak.expired());
REQUIRE(interfaceWeak.expired());
REQUIRE(worldWeak.expired());
}
@ -394,7 +492,7 @@ TEMPLATE_TEST_CASE("Create world and rail vehicle => destroy world", "[object-cr
REQUIRE(worldWeak.expired());
}
TEMPLATE_TEST_CASE("Create world and rail vehicle => destroy interface", "[object-create-destroy]", RAIL_VEHICLES)
TEMPLATE_TEST_CASE("Create world and rail vehicle => destroy rail vehicle", "[object-create-destroy]", RAIL_VEHICLES)
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;

Datei anzeigen

@ -44,11 +44,14 @@ TEST_CASE("Train: Save/Load", "[train][train-saveload]")
{
worldUUID = world->uuid;
auto decoder = world->decoders->create();
REQUIRE(world->decoders->length == 0);
REQUIRE(world->railVehicles->length == 0);
auto locomotive = world->railVehicles->create(Locomotive::classId);
locomotive->decoder = decoder;
REQUIRE(decoder->vehicle.value() == locomotive);
REQUIRE(world->decoders->length == 1);
REQUIRE(world->railVehicles->length == 1);
REQUIRE(locomotive->decoder);
REQUIRE(locomotive->decoder->vehicle.value() == locomotive);
auto train = world->trains->create();
train->vehicles->add(locomotive);