A simple EditDialog template

published at 29.03.2018 15:32 by Jens Weller
Save to Instapaper Pocket

So far I covered the basics for connecting boost fusion adapted structs with Qts Model/View architecture. Today is the next step: a simple dialog for editing a single instance of such a fusion enabled type.

This is only a simple form dialog, where each row is a label and a widget for data entry. But it covers the necessary code to generate exactuly this input form from the information provided by fusion and the tags.

Dialog basics

This dialog has two roles: one for editing an existing instance, it could be very useful for displaying an options dialog. Where the options class is a fusion adapted struct. Or, for entering new data and then turning this data into an instance of the fusion enabled type. The basic layout of the EditDialog looks like this:

template< class Seq, typename ...Tags>
class EditDialog : public QDialog
{
    W_OBJECT(EditDialog) //Q_OBJECT for templates from verdigris
    using taglist = boost::mp11::mp_list< Tags...>;
    const size_t colnumber = uitags::count_editable_tags< Tags...>();
    std::array<int, uitags::count_editable_tags< Tags...>()> index_array = uitags::make_edit_index_array< Tags...>();
    std::array< const char*,boost::fusion::result_of::size< Seq>::value> membernames = tagtype::get_member_names< Seq>();
    std::array< QWidget*,boost::fusion::result_of::size< Seq>::value> index2widget;
    std::array< QLabel*,boost::fusion::result_of::size< Seq>::value> index2label;

Lots of std::array members:

As this template has two constructors, there is a private function to construct the UI:

void makeDialog()
{
    QVBoxLayout* vbox = new QVBoxLayout(this);
    setLayout(vbox);
    QFormLayout* form_layout = new QFormLayout();
    form_layout->setSizeConstraint(QLayout::SetMinAndMaxSize);
    boost::mp11::mp_for_each< boost::mp11::mp_iota_c<uitags::count_editable_tags< Tags...>()>>(
                [&]( auto I ){
                if(index_array[I]!= -1)
                {
                  QWidget* w = make_widget(this,boost::mp11::mp_at_c< taglist,I>{});
                  index2widget[I]=w;
                  QLabel *lbl = new QLabel(QString("Enter ")+ membernames[I]);
                  index2label[I] = lbl;
                  form_layout->addRow(lbl,w);
                }
                } );
    vbox->addLayout(form_layout);

    auto buttonBox = new QDialogButtonBox(this);

    buttonBox->setOrientation(Qt::Horizontal);
    buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
    connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
    connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
    vbox->addWidget(buttonBox);
}

I'm used to click my UIs together in QtCreator, so writing manual UI building code in Qt is not a thing I do very often. So this is regular UI code which you normally would not have write by your self in Qt. But as this is a template, one has to write this code only once.

The only non Qt part of this code is where the actual widgets and labels are created by calling mp_for_each, which then calls the generic lambda once for each index. The make_widget function uses tag dispatch to create the correct widget type for each tag. Boosts mp11::mp_at_c is used to access and create the correct tag type.

There is a 1:1 correlation between tag and widgettype:

QWidget* make_widget(QWidget* parent,uitags::SingleLine)
{
    return new QLineEdit(parent);
}

Similar functions handle the retrieving and setting values to these widgets. QWidget has no general API for this. Each widget has its own method called value, text, toPlainText (,...) to get the actual value inside what is displayed/edited. Hence also each tag needs a function for setting/getting the value from its widget:

QVariant get_value_as_variant(const QWidget* w,uitags::DoubleSpinBox)
{
    return qobject_cast< const QDoubleSpinBox*>(w)->value();
}

void set_value(QWidget* w,const std::string& s,uitags::SingleLine)
{
    qobject_cast< QLineEdit*>(w)->setText(QString::fromStdString(s));
}

void set_value(QWidget* w,const QString& s,uitags::SingleLine)
{
    qobject_cast< QLineEdit*>(w)->setText(s);
}

For the value retrieval QVariant is used, as Qt types already convert easily to it, and there is already code with can assign a QVariant to a fusion enabled struct. For the set_value functions, several overloads are provided, as these might be called with the actual type.

The logic of an EditDialog is, that nothing gets changed until Ok is pressed, cancel will keep the old values. I decided to now provide an automatic assignment to the values once OK is pressed, the handling code still has to call transferValues:

void transferValues(Seq& s)
{
    boost::mp11::mp_for_each< boost::mp11::mp_iota_c< uitags::count_editable_tags()>>(
                [&]( auto I ){
                    if(index_array[I]!= -1)
                    {
                     QWidget* w = index2widget[I];
                     qt_assign(boost::fusion::get< I>(s),get_value_as_variant(w,boost::mp11::mp_at_c< taglist,I>{}));
                    }
                } );
}

This utilizes again mp11::mp_for_each to iterate over the index of the struct members from fusion. The in the previous post introduced qt_assign function handles the setting of the value.

And this is pretty much the code for the edit dialog. But this is also how my code has explored this new way of writing Qt UI Code for C++ domain classes. Right now the code is a good prototype to write prototype like UIs. I'll explore where this path leads beyond the simple approach in the next weeks...

 

Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!