traintastic/client/src/widget/outputmapwidget.cpp
2024-10-21 22:16:12 +02:00

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;
});
}
}