365 Zeilen
12 KiB
C++
365 Zeilen
12 KiB
C++
/**
|
|
* client/src/widget/outputmapwidget.cpp
|
|
*
|
|
* This file is part of the traintastic source code.
|
|
*
|
|
* Copyright (C) 2021,2024 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 "outputmapwidget.hpp"
|
|
#include <QVBoxLayout>
|
|
#include <QFormLayout>
|
|
#include <QToolBar>
|
|
#include <QTableWidget>
|
|
#include <QHeaderView>
|
|
#include <QPushButton>
|
|
#include <traintastic/locale/locale.hpp>
|
|
#include "interfaceitemnamelabel.hpp"
|
|
#include "propertycheckbox.hpp"
|
|
#include "propertycombobox.hpp"
|
|
#include "propertyspinbox.hpp"
|
|
#include "objectpropertycombobox.hpp"
|
|
#include "propertyaddresses.hpp"
|
|
#include "outputmapoutputactionwidget.hpp"
|
|
#include "../board/tilepainter.hpp"
|
|
#include "../board/getboardcolorscheme.hpp"
|
|
#include "../dialog/objectselectlistdialog.hpp"
|
|
#include "../network/callmethod.hpp"
|
|
#include "../network/method.hpp"
|
|
#include "../network/property.hpp"
|
|
#include "../network/objectproperty.hpp"
|
|
#include "../network/vectorproperty.hpp"
|
|
#include "../network/objectvectorproperty.hpp"
|
|
#include "../utils/enum.hpp"
|
|
#include "../theme/theme.hpp"
|
|
#include "../misc/methodaction.hpp"
|
|
|
|
constexpr int columnKey = 0;
|
|
|
|
static bool hasUseColumn(const QString& classId)
|
|
{
|
|
return classId == "output_map.signal";
|
|
}
|
|
|
|
static void setComboBoxMinimumWidth(QComboBox* comboBox)
|
|
{
|
|
comboBox->setMinimumWidth(25 * comboBox->fontMetrics().averageCharWidth());
|
|
}
|
|
|
|
OutputMapWidget::OutputMapWidget(ObjectPtr object, QWidget* parent)
|
|
: QWidget(parent)
|
|
, m_object{std::move(object)}
|
|
, m_hasUseColumn{hasUseColumn(m_object->classId())}
|
|
, m_columnCountNonOutput{m_hasUseColumn ? 2 : 1}
|
|
, m_addresses{m_object->getVectorProperty("addresses")}
|
|
, m_ecosObject{dynamic_cast<Property*>(m_object->getProperty("ecos_object"))}
|
|
, m_items{m_object->getObjectVectorProperty("items")}
|
|
, m_table{new QTableWidget(this)}
|
|
, m_getParentRequestId{Connection::invalidRequestId}
|
|
, m_getItemsRequestId{Connection::invalidRequestId}
|
|
{
|
|
QVBoxLayout* l = new QVBoxLayout();
|
|
|
|
QFormLayout* form = new QFormLayout();
|
|
if(auto* interface = dynamic_cast<ObjectProperty*>(m_object->getProperty("interface")))
|
|
{
|
|
auto* comboBox = new ObjectPropertyComboBox(*interface, this);
|
|
setComboBoxMinimumWidth(comboBox);
|
|
form->addRow(new InterfaceItemNameLabel(*interface, this), comboBox);
|
|
}
|
|
if(auto* channel = dynamic_cast<Property*>(m_object->getProperty("channel")))
|
|
{
|
|
auto* comboBox = new PropertyComboBox(*channel, this);
|
|
setComboBoxMinimumWidth(comboBox);
|
|
form->addRow(new InterfaceItemNameLabel(*channel, this), comboBox);
|
|
}
|
|
if(m_addresses)
|
|
{
|
|
form->addRow(new InterfaceItemNameLabel(*m_addresses, this), new PropertyAddresses(*m_addresses, m_object->getMethod("add_address"), m_object->getMethod("remove_address"), this));
|
|
connect(m_addresses, &AbstractVectorProperty::valueChanged, this, &OutputMapWidget::updateTableOutputColumns);
|
|
}
|
|
if(m_ecosObject)
|
|
{
|
|
auto* comboBox = new PropertyComboBox(*m_ecosObject, this);
|
|
setComboBoxMinimumWidth(comboBox);
|
|
form->addRow(new InterfaceItemNameLabel(*m_ecosObject, this), comboBox);
|
|
connect(m_ecosObject, &AbstractVectorProperty::valueChanged, this, &OutputMapWidget::updateTableOutputColumns);
|
|
}
|
|
l->addLayout(form);
|
|
|
|
const int listViewIconSize = m_table->style()->pixelMetric(QStyle::PM_ListViewIconSize);
|
|
m_table->setIconSize({listViewIconSize, listViewIconSize});
|
|
m_table->setColumnCount(m_columnCountNonOutput);
|
|
m_table->setRowCount(0);
|
|
QStringList labels;
|
|
labels.append(Locale::tr(m_object->classId() + ":key"));
|
|
if(m_hasUseColumn)
|
|
{
|
|
labels.append(Locale::tr("output_map:use"));
|
|
}
|
|
m_table->setHorizontalHeaderLabels(labels);
|
|
m_table->verticalHeader()->setVisible(false);
|
|
m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
|
|
|
l->addWidget(m_table);
|
|
|
|
setLayout(l);
|
|
|
|
if(auto* parentObject = m_object->getObjectProperty("parent"))
|
|
{
|
|
m_getParentRequestId = parentObject->getObject(
|
|
[this](const ObjectPtr& obj, std::optional<const Error> ec)
|
|
{
|
|
if(obj && !ec)
|
|
{
|
|
m_getParentRequestId = Connection::invalidRequestId;
|
|
m_parentObject = obj;
|
|
updateKeyIcons();
|
|
}
|
|
});
|
|
}
|
|
|
|
if(m_items) /*[[likely]]*/
|
|
{
|
|
m_getItemsRequestId = m_items->getObjects(
|
|
[this](const std::vector<ObjectPtr>& objects, std::optional<const Error> ec)
|
|
{
|
|
if(!ec)
|
|
{
|
|
updateItems(objects);
|
|
}
|
|
});
|
|
|
|
connect(&BoardSettings::instance(), &BoardSettings::changed, this, &OutputMapWidget::updateKeyIcons);
|
|
}
|
|
}
|
|
|
|
OutputMapWidget::~OutputMapWidget()
|
|
{
|
|
if(m_getParentRequestId != Connection::invalidRequestId)
|
|
{
|
|
m_object->connection()->cancelRequest(m_getParentRequestId);
|
|
}
|
|
if(m_getItemsRequestId != Connection::invalidRequestId)
|
|
{
|
|
m_object->connection()->cancelRequest(m_getItemsRequestId);
|
|
}
|
|
}
|
|
|
|
void OutputMapWidget::updateItems(const std::vector<ObjectPtr>& items)
|
|
{
|
|
m_table->setRowCount(static_cast<int>(items.size()));
|
|
m_itemObjects = items;
|
|
m_actions.resize(items.size());
|
|
for(size_t i = 0; i < items.size(); i++)
|
|
{
|
|
if(auto* p = items[i]->getProperty("key"))
|
|
{
|
|
QString text;
|
|
if(p->type() == ValueType::Enum)
|
|
{
|
|
text = translateEnum(*p);
|
|
}
|
|
else if(p->type() == ValueType::Boolean)
|
|
{
|
|
text = p->toBool() ? "true" : "false";
|
|
|
|
if(p->hasAttribute(AttributeName::AliasKeys))
|
|
{
|
|
const QVariantList aliasKeys = p->getAttribute(AttributeName::AliasKeys, QVariant()).toList();
|
|
const QVariantList aliasValues = p->getAttribute(AttributeName::AliasValues, QVariant()).toList();
|
|
|
|
if(aliasKeys.size() == aliasValues.size()) /*[[likely]]*/
|
|
{
|
|
if(int index = aliasKeys.indexOf(p->toBool()); index != -1)
|
|
{
|
|
text = Locale::instance->parse(aliasValues[index].toString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else /*[[unlikely]]*/
|
|
{
|
|
assert(false);
|
|
text = "?";
|
|
}
|
|
m_table->setItem(static_cast<int>(i), columnKey, new QTableWidgetItem(text));
|
|
}
|
|
|
|
if(m_hasUseColumn)
|
|
{
|
|
const int columnUse = columnKey + 1;
|
|
|
|
if(auto* p = dynamic_cast<Property*>(items[i]->getProperty("use")))
|
|
{
|
|
QWidget* w = new QWidget(m_table);
|
|
QHBoxLayout* l = new QHBoxLayout();
|
|
l->setAlignment(Qt::AlignCenter);
|
|
l->addWidget(new PropertyCheckBox(*p, w));
|
|
w->setLayout(l);
|
|
m_table->setCellWidget(static_cast<int>(i), columnUse, w);
|
|
}
|
|
}
|
|
|
|
if(auto* p = items[i]->getProperty("visible"))
|
|
{
|
|
m_table->setRowHidden(static_cast<int>(i), !p->toBool());
|
|
|
|
connect(p, &Property::valueChangedBool, this,
|
|
[this, row=static_cast<int>(i)](bool value)
|
|
{
|
|
m_table->setRowHidden(row, !value);
|
|
});
|
|
}
|
|
|
|
if(auto* outputActions = dynamic_cast<ObjectVectorProperty*>(items[i]->getVectorProperty("output_actions")))
|
|
{
|
|
updateTableOutputActions(*outputActions, static_cast<int>(i));
|
|
|
|
connect(outputActions, &ObjectVectorProperty::valueChanged, this,
|
|
[this, row=static_cast<int>(i)]()
|
|
{
|
|
updateTableOutputActions(*dynamic_cast<ObjectVectorProperty*>(sender()), row);
|
|
});
|
|
}
|
|
}
|
|
|
|
updateKeyIcons();
|
|
updateTableOutputColumns();
|
|
}
|
|
|
|
void OutputMapWidget::updateKeyIcons()
|
|
{
|
|
if(!m_parentObject)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(auto tileIdProperty = m_parentObject->getProperty("tile_id"))
|
|
{
|
|
const bool darkBackground = m_table->palette().window().color().lightnessF() < 0.5;
|
|
const auto tileId = tileIdProperty->toEnum<TileId>();
|
|
|
|
const int iconSize = m_table->iconSize().height();
|
|
QImage image(iconSize, iconSize, QImage::Format_ARGB32);
|
|
QPainter painter{&image};
|
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
|
TilePainter tilePainter{painter, iconSize, *getBoardColorScheme(darkBackground ? BoardSettings::ColorScheme::Dark : BoardSettings::ColorScheme::Light)};
|
|
|
|
for(size_t i = 0; i < m_itemObjects.size(); i++)
|
|
{
|
|
if(auto* key = m_itemObjects[i]->getProperty("key"))
|
|
{
|
|
image.fill(Qt::transparent);
|
|
|
|
if(isRailTurnout(tileId))
|
|
{
|
|
tilePainter.drawTurnout(tileId, image.rect(), TileRotate::Deg0, TurnoutPosition::Unknown, static_cast<TurnoutPosition>(key->toInt()));
|
|
}
|
|
else if(isRailSignal(tileId))
|
|
{
|
|
tilePainter.drawSignal(tileId, image.rect(), TileRotate::Deg0, false, static_cast<SignalAspect>(key->toInt()));
|
|
}
|
|
else if(tileId == TileId::RailDirectionControl)
|
|
{
|
|
tilePainter.drawDirectionControl(tileId, image.rect(), TileRotate::Deg0, false, static_cast<DirectionControlState>(key->toInt()));
|
|
}
|
|
else if(tileId == TileId::RailDecoupler)
|
|
{
|
|
tilePainter.drawRailDecoupler(image.rect(), TileRotate::Deg90, false, static_cast<DecouplerState>(key->toInt()));
|
|
}
|
|
else if(tileId == TileId::Switch)
|
|
{
|
|
tilePainter.drawSwitch(image.rect(), key->toBool());
|
|
}
|
|
else
|
|
{
|
|
break; // tileId not supported (yet)
|
|
}
|
|
|
|
m_table->item(static_cast<int>(i), columnKey)->setIcon(QPixmap::fromImage(image));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OutputMapWidget::updateTableOutputColumns()
|
|
{
|
|
if(m_addresses && m_addresses->getAttributeBool(AttributeName::Visible, true))
|
|
{
|
|
const auto size = m_addresses->size();
|
|
|
|
m_table->setColumnCount(m_columnCountNonOutput + size);
|
|
for(int i = 0; i < size; i++)
|
|
{
|
|
const int column = m_columnCountNonOutput + i;
|
|
const int address = m_addresses->getInt(i);
|
|
auto* item = new QTableWidgetItem(QString("#%1").arg(address));
|
|
item->setToolTip(Locale::tr("output_map:address_x").arg(address));
|
|
m_table->setHorizontalHeaderItem(column, item);
|
|
}
|
|
}
|
|
else if(m_ecosObject && m_ecosObject->getAttributeBool(AttributeName::Visible, true))
|
|
{
|
|
m_table->setColumnCount(m_columnCountNonOutput + 1);
|
|
m_table->setHorizontalHeaderItem(m_columnCountNonOutput, new QTableWidgetItem(Locale::tr("output.ecos_object:state")));
|
|
}
|
|
else
|
|
{
|
|
m_table->setColumnCount(m_columnCountNonOutput);
|
|
}
|
|
}
|
|
|
|
void OutputMapWidget::updateTableOutputActions(ObjectVectorProperty& property, int row)
|
|
{
|
|
if(!property.empty())
|
|
{
|
|
m_dummy = property.getObjects(
|
|
[this, row](const std::vector<ObjectPtr>& objects, std::optional<const Error> /*ec*/)
|
|
{
|
|
const int columnCount = static_cast<int>(m_columnCountNonOutput + objects.size());
|
|
if(columnCount > m_table->columnCount())
|
|
{
|
|
m_table->setColumnCount(columnCount);
|
|
}
|
|
|
|
auto& rowActions = m_actions[row];
|
|
int column = m_columnCountNonOutput;
|
|
for(auto& object : objects)
|
|
{
|
|
if(column >= static_cast<int>(rowActions.size()) || object.get() != rowActions[column].get())
|
|
{
|
|
if(auto* action = dynamic_cast<Property*>(object->getProperty("action")))
|
|
{
|
|
m_table->setCellWidget(row, column, new PropertyComboBox(*action, this));
|
|
}
|
|
else if(auto* aspect = dynamic_cast<Property*>(object->getProperty("aspect")))
|
|
{
|
|
m_table->setCellWidget(row, column, new PropertySpinBox(*aspect, this));
|
|
}
|
|
else if(auto* state = dynamic_cast<Property*>(object->getProperty("state")))
|
|
{
|
|
m_table->setCellWidget(row, column, new PropertySpinBox(*state, this));
|
|
}
|
|
}
|
|
column++;
|
|
}
|
|
rowActions = objects;
|
|
});
|
|
}
|
|
}
|