Skip to content

Commit

Permalink
Merge pull request #40 from hasselmm/introduce-serial-list-model
Browse files Browse the repository at this point in the history
Introduce serial port list model
  • Loading branch information
hasselmm committed Jul 8, 2023
2 parents e5d61aa + d6bbc7d commit a8ee536
Show file tree
Hide file tree
Showing 18 changed files with 354 additions and 96 deletions.
10 changes: 6 additions & 4 deletions apps/lmrstudio/deviceparameterwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class DeviceParameterWidget::Private : public core::PrivateObject<DeviceParamete
explicit Private(core::DeviceFactory *factory, DeviceParameterWidget *d)
: PrivateObject{d}
, factory{factory}
, parameters{factory->parameters()}
{}

Editor createEditor(core::Parameter parameter);
Expand All @@ -68,6 +69,7 @@ class DeviceParameterWidget::Private : public core::PrivateObject<DeviceParamete
void revalidate();

QPointer<core::DeviceFactory> const factory;
const QList<core::Parameter> parameters;
QMap<QByteArray, Editor> editors;
bool hasAcceptableInput = true;
};
Expand Down Expand Up @@ -104,9 +106,9 @@ DeviceParameterWidget::Private::Editor DeviceParameterWidget::Private::createCho
if (const auto choices = model.toStringList(); !choices.isEmpty()) {
for (const auto &text: choices)
editor->addItem(text, text);
} else if (const auto choiceModel = model.value<core::parameters::ChoiceModel>(); !choiceModel.choices.isEmpty()) {
for (const auto &choice: choiceModel.choices)
editor->addItem(choice.text, choice.value);
} else if (const auto choiceModel = model.value<core::parameters::ChoiceModel>();
choiceModel.isValid() && choiceModel.choices()) {
editor->setModel(choiceModel.choices());
} else {
qCWarning(logger(), "Unsupported type %s for values of choices parameter", model.typeName());
}
Expand Down Expand Up @@ -198,7 +200,7 @@ DeviceParameterWidget::DeviceParameterWidget(core::DeviceFactory *factory, QWidg
const auto layout = new QFormLayout{this};
layout->setContentsMargins({});

for (const auto &parameter: d->factory->parameters()) {
for (const auto &parameter: std::as_const(d->parameters)) {
if (auto editor = d->createEditor(parameter); editor.widget) {
auto label = new l10n::Facade<QLabel>{parameter.name(), this};
layout->addRow(label, editor.widget);
Expand Down
2 changes: 1 addition & 1 deletion apps/lmrstudio/qml/AutomationParameterEditor.qml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Item {
id: choiceEditor

// FIXME: handle optional parameters
model: editor.parameter.model.qmlChoices
model: editor.parameter.model.choices

textRole: "text"
valueRole: "value"
Expand Down
1 change: 1 addition & 0 deletions lmrs/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
add_subdirectory(core)
add_subdirectory(gui)
add_subdirectory(serial)
add_subdirectory(widgets)

add_subdirectory(esu)
Expand Down
2 changes: 1 addition & 1 deletion lmrs/core/framestream.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class FrameStreamReader
bool isAtEnd() const;
bool readNext(int minimumSize = 0);

QByteArray frame() const;
QByteArray frame() const;
qsizetype bufferedBytes() const;

private:
Expand Down
118 changes: 112 additions & 6 deletions lmrs/core/parameters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,110 @@ QByteArray prefixedKey(QByteArray key, QByteArray prefix)

} // namespace

QVariant parameters::ChoiceListModel::data(const QModelIndex &index, int role) const
{
if (hasIndex(index.row(), index.column(), index.parent())) {
switch (static_cast<DataRole>(role)) {
case TextRole:
return m_choices[index.row()].text;
case ValueRole:
return m_choices[index.row()].value;
}
}

return {};
}

int parameters::ChoiceListModel::rowCount(const QModelIndex &parent) const
{
if (Q_UNLIKELY(parent.isValid()))
return 0;

return static_cast<int>(m_choices.count());
}

QHash<int, QByteArray> parameters::ChoiceListModel::roleNames() const
{
return {
{TextRole, "text"},
{ValueRole, "value"},
};
}

int parameters::ChoiceListModel::findValue(QVariant value) const
{
const auto sameByValue = [value](const Choice &choice) {
return choice.value == value;
};

const auto first = m_choices.constBegin();
const auto last = m_choices.constEnd();

if (const auto it = std::find_if(first, last, sameByValue); it != last)
return static_cast<int>(it - first);

return -1;
}

QVariantList parameters::ChoiceListModel::values() const
{
auto values = QVariantList{};
values.reserve(m_choices.size());

std::transform(m_choices.constBegin(), m_choices.constEnd(), std::back_inserter(values),
[](const Choice &choice) { return choice.value; });

return values;
}

void parameters::ChoiceListModel::append(QString text, QVariant value)
{
const auto nextRow = static_cast<int>(m_choices.count());

beginInsertRows({}, nextRow, nextRow);
m_choices.emplaceBack(std::move(text), std::move(value));
endInsertRows();
}

void parameters::ChoiceListModel::removeAll(QVariant value)
{
for (auto it = m_choices.begin(); it != m_choices.end(); ) {
if (it->value == value) {
const auto row = static_cast<int>(it - m_choices.begin());
beginRemoveRows({}, row, row);
it = m_choices.erase(it);
endRemoveRows();
} else {
++it;
}
}
}

void parameters::ChoiceListModel::setChoices(QList<Choice> choices)
{
beginResetModel();
m_choices = std::move(choices);
endResetModel();
}

void parameters::ChoiceListModel::setChoices(QVariantList values, std::function<QString (QVariant)> makeText)
{
if (!makeText) {
setChoices(values, [](auto value) { return value.toString(); });
return;
}

auto choices = QList<Choice>{};
choices.reserve(values.count());

std::transform(values.constBegin(), values.constEnd(), std::back_inserter(choices), [makeText](auto value) {
auto text = makeText(value);
return Choice{std::move(text), std::move(value)};
});

setChoices(std::move(choices));
}

auto &Parameter::logger()
{
return core::logger<Parameter>();
Expand Down Expand Up @@ -170,17 +274,19 @@ QJsonValue Parameter::toJson(QVariant value) const

Parameter Parameter::choice(QByteArrayView key, l10n::String name, parameters::ChoiceModel model, Flags flags)
{
if (LMRS_FAILED(logger(), !model.choices.isEmpty())
|| LMRS_FAILED(logger(), model.valueType.isValid())) {
if (LMRS_FAILED(logger(), model.isValid())
|| LMRS_FAILED(logger(), model.choices())) {
qCCritical(logger(), "Invalid choice model for parameter \"%s\"", key.constData());
return {};
}

for (auto it = model.choices.begin(); it != model.choices.end(); ++it) {
if (LMRS_FAILED_EQUALS(logger(), it->value.metaType(), model.valueType)) {
for (const auto &index: model.choices()) {
const auto value = ChoiceListModel::value(index);

if (LMRS_FAILED_EQUALS(logger(), value.metaType(), model.valueType)) {
qCCritical(logger(), "Choice \"%ls\" at index %d has invalid type %s (expected: %s)",
qUtf16Printable(it->text), static_cast<int>(it - model.choices.begin()),
it->value.typeName(), model.valueType.name());
qUtf16Printable(ChoiceListModel::text(index)), index.row(),
value.typeName(), model.valueType.name());
}
}

Expand Down
111 changes: 87 additions & 24 deletions lmrs/core/parameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,6 @@ class QHostAddress;

namespace lmrs::core::parameters {

struct ParameterModel
{
Q_GADGET

public:
constexpr ParameterModel(QMetaType valueType = {})
: valueType{std::move(valueType)}
{}

QMetaType valueType;
};

struct Choice
{
Q_GADGET
Expand All @@ -39,22 +27,85 @@ struct Choice
QVariant value;
};

class ChoiceListModel : public QAbstractListModel
{
Q_OBJECT

public:
enum DataRole {
TextRole = Qt::DisplayRole,
ValueRole = Qt::UserRole,
};

explicit ChoiceListModel(QMetaType valueType, QObject *parent = nullptr)
: QAbstractListModel{parent}
, m_valueType{std::move(valueType)}
{}

explicit ChoiceListModel(QMetaType valueType, QList<Choice> choices, QObject *parent = nullptr)
: QAbstractListModel{parent}
, m_valueType{std::move(valueType)}
, m_choices{std::move(choices)}
{}

[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
[[nodiscard]] int rowCount(const QModelIndex &parent = {}) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;

[[nodiscard]] auto valueType() const { return m_valueType; }
[[nodiscard]] int findValue(QVariant value) const;
[[nodiscard]] QVariantList values() const;

void append(QString text, QVariant value);
void removeAll(QVariant value);

void setChoices(QList<Choice> choices);
void setChoices(QVariantList values, std::function<QString (QVariant)> makeText = {});

[[nodiscard]] static auto text(const QModelIndex &index) { return index.data(TextRole).toString(); }
[[nodiscard]] static auto value(const QModelIndex &index) { return index.data(ValueRole); }

private:
QMetaType m_valueType;
QList<Choice> m_choices;
};

struct ParameterModel
{
Q_GADGET

public:
constexpr ParameterModel(QMetaType valueType = {})
: valueType{std::move(valueType)}
{}

[[nodiscard]] auto isValid() const { return valueType.isValid(); }

QMetaType valueType;
};

struct ChoiceModel : public ParameterModel
{
Q_GADGET
Q_PROPERTY(QList<lmrs::core::parameters::Choice> choices MEMBER choices CONSTANT FINAL)
Q_PROPERTY(QVariantList qmlChoices READ qmlChoices CONSTANT FINAL)
Q_PROPERTY(lmrs::core::parameters::ChoiceListModel *choices READ choices CONSTANT FINAL)

public:
QList<Choice> choices;
constexpr ChoiceModel() = default;

QVariantList qmlChoices() const
{
auto result = QVariantList{};
result.reserve(choices.size());
std::transform(choices.begin(), choices.end(), std::back_inserter(result), &QVariant::fromValue<Choice>);
return result;
}
ChoiceModel(QMetaType valueType, QList<Choice> choices)
: ParameterModel{std::move(valueType)}
, m_choices{std::make_shared<ChoiceListModel>(std::move(valueType), std::move(choices))}
{}

ChoiceModel(QMetaType valueType, std::shared_ptr<ChoiceListModel> choices)
: ParameterModel{std::move(valueType)}
, m_choices{std::move(choices)}
{}

ChoiceListModel *choices() const { return m_choices.get(); }

private:
std::shared_ptr<ChoiceListModel> m_choices;
};

struct NumberModel : public ParameterModel
Expand Down Expand Up @@ -174,6 +225,13 @@ struct Parameter
Flags flags = {}); // FIXME: make this a generic value type


[[nodiscard]] static Parameter choice(QByteArrayView key, l10n::String name,
std::shared_ptr<ChoiceListModel> choices, Flags flags = {})
{
auto model = ChoiceModel{choices->valueType(), std::move(choices)};
return choice(std::move(key), std::move(name), std::move(model), std::move(flags));
}

template<class T>
[[nodiscard]] static Parameter choice(QByteArrayView key, l10n::String name, QList<Choice> choices, Flags flags = {})
{
Expand Down Expand Up @@ -206,8 +264,13 @@ struct Parameter
static auto &logger(auto) = delete;
[[nodiscard]] static auto &logger();

[[nodiscard]] static Parameter text(QByteArrayView key, l10n::String name, QMetaType type, TextModel model, Flags flags);
[[nodiscard]] static Parameter choice(QByteArrayView key, l10n::String name, QMetaType type, QMetaEnum values, Flags flags);
[[nodiscard]] static Parameter text(QByteArrayView key, l10n::String name, QMetaType type,
TextModel model, Flags flags);

[[nodiscard]] static Parameter choice(QByteArrayView key, l10n::String name, QMetaType type,
QMetaEnum values, Flags flags);
[[nodiscard]] static Parameter choice(QByteArrayView key, l10n::String name, QMetaType type,
std::shared_ptr<ChoiceListModel> choices, Flags flags);

Type m_type = Type::Invalid;
Flags m_flags;
Expand Down
2 changes: 1 addition & 1 deletion lmrs/esu/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ add_library (
)

target_include_directories(LmrsEsu PUBLIC ${CMAKE_SOURCE_DIR})
target_link_libraries(LmrsEsu PUBLIC Lmrs::Core Qt6::SerialPort)
target_link_libraries(LmrsEsu PUBLIC Lmrs::Serial)

add_library(Lmrs::Esu ALIAS LmrsEsu)
lmrs_doxygen_add_target(LmrsEsu)
Expand Down
21 changes: 4 additions & 17 deletions lmrs/esu/lp2device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <lmrs/core/userliterals.h>
#include <lmrs/core/validatingvariantmap.h>

#include <lmrs/serial/serialportmodel.h>

#include <QEvent>
#include <QSerialPort>
#include <QSerialPortInfo>
Expand All @@ -22,22 +24,6 @@ namespace {

constexpr auto s_parameter_portName = "port"_BV;

auto defaultPorts()
{
auto serialPorts = QList<core::parameters::Choice>{};

for (const auto &info: QSerialPortInfo::availablePorts()) {
auto description = info.portName();

if (auto text = info.description(); !text.isEmpty())
description += " ("_L1 + text + ')'_L1;

serialPorts.emplaceBack(std::move(description), info.portName());
}

return serialPorts;
}

constexpr auto makeError(Response::Status status)
{
switch (status) {
Expand Down Expand Up @@ -912,7 +898,8 @@ QString DeviceFactory::name() const
QList<core::Parameter> DeviceFactory::parameters() const
{
return {
core::Parameter::choice<QString>(s_parameter_portName, LMRS_TR("Serial &port:"), defaultPorts()),
core::Parameter::choice(s_parameter_portName, LMRS_TR("Serial &port:"),
std::make_shared<serial::SerialPortModel>()),
};
}

Expand Down

0 comments on commit a8ee536

Please sign in to comment.