Enginio C++ Examples - Todos

In this example, a list of objects is displayed in a QTreeView. Each item in the list is a Todo object, which can be marked Done or Not done. A Todo can be added, removed, or altered. We will be using the classes and concepts duscussed in Model/View Programming.

In this simple schema, each ToDo object will have two properties: a string named title and a bool named completed. These properties are added to the default properties e.g.: creation date, which always exist.

A Todo object will look like this in JSON:

{
  "title": "Buy Milk",
  "completed": false
}

A ToDo object can be created and appended to an EnginioModel using append(), as shown in MainWindow::appendItem(). Alternatively, if many ToDo objects are to be added to the model all at once, they can be inserted directly into the server database via the model's Enginio client. The client is obtained from the model with client(). Then the ToDo objects are inserted into the server database with the client's create() function. The model can then be reloaded from the server with reload().

But first we must create some infrastructure. Our TodosModel class is declared in todos-cpp/todosmodel.h. It inherits EnginioModel, which is a list model. We add two roles to the Enginio::Role enum, chosen for the new properties, one for the ToDo title and one for marking a ToDo as completed. We assign enum values to the new roles, equating TitleRole to Enginio::CustomPropertyRole, which is itself equated to Qt::UserRole + 10.

class TodosModel : public EnginioModel
{
    Q_OBJECT

public:
    enum Role
    {
        TitleRole = Enginio::CustomPropertyRole,
        CompletedRole
    };

The new roles can be used for displaying and editing the values of the new properties. Views of the model also use roles from in Qt::ItemDataRole enum. The example's view is a QTreeView, as shown in the MainWindow class, which holds a pointer to the view, a pointer to the client, and a pointer to the model.

A new EnginioModel is empty. It automatically populates itself from the server, when its query and client properties have both been set. In the example, these properties are set in the constructor for the main window. The EnginioClient is created first. Then an instance of the ToDosModel is created, and its client is set using EnginioModel::setClient(). Then the query is created as a QJsonObject, and the model's query is set using EnginioModel::setQuery().

Once the ToDo data has been downloaded, the model resets itself, and sets up the internal data cache and roles names. EnginioModel guesses the role names based on heuristics. It may be wrong if not all objects received from the backend have exactly the same structure. For example, a property can be missing in certain objects. To protect against such cases, we overload roleNames(). Overriding roleNames() can also be used to match default Qt roles to the named ones.

QHash<int, QByteArray> TodosModel::roleNames() const
{
    QHash<int, QByteArray> roles = EnginioModel::roleNames();
    roles.insert(TitleRole, "title");
    roles.insert(Qt::DisplayRole, "title");
    roles.insert(Qt::EditRole, "title");
    roles.insert(CompletedRole, "completed");
    return roles;
}

In this example, we map the Qt::DisplayRole and Qt::EditRole to the title property in the JSON. This way the right string is shown by default and editing works as expected.

Remember to always call the base class implementation to avoid situations in which the internal cache is not in sync.

By default EnginioModel operates on QJsonValue, and that is what the data() function returns inside the QVariant, but standard views, such as QListView, use predefined roles which do not map directly to our roles. That is why we need to write a mapping between them:

QVariant TodosModel::data(const QModelIndex &index, int role) const
{
    if (role == Qt::FontRole) {
        bool completed = EnginioModel::data(index, CompletedRole).value<QJsonValue>().toBool();
        QFont font;
        font.setPointSize(20);
        font.setStrikeOut(completed);
        return font;
    }

    if (role == Qt::TextColorRole) {
        bool completed = EnginioModel::data(index, CompletedRole).value<QJsonValue>().toBool();
        return completed ? QColor("#999") : QColor("#333");
    }

    if (role == CompletedRole)
        return EnginioModel::data(index, CompletedRole).value<QJsonValue>().toBool();

    // fallback to base class
    return EnginioModel::data(index, role);
}

As we have our model defined, we need to create an instance of EnginioClient:

m_client = new EnginioClient(this);
m_client->setBackendId(EnginioBackendId);

It is used by the model to connect to the Enginio backend. Next we need to construct and configure our model too. The configuration is based on two steps, assigning an EnginioClient instance and by creating a query.

m_model = new TodosModel(this);
m_model->setClient(m_client);

QJsonObject query;
query["objectType"] = QString::fromUtf8("objects.todos");
m_model->setQuery(query);

The model has to be assigned to a view. In this example it is a QTreeView.

m_view->setModel(m_model);

To make the application fully functional, a way to add and remove a Todo is needed. To do so, we need to connect the correct buttons to slots for adding a new item:

void MainWindow::appendItem()
{
    bool ok;
    QString text = QInputDialog::getText(this, tr("Create a new To Do")
                                         , tr("Title:"), QLineEdit::Normal
                                         , "", &ok);
    if (ok && !text.isEmpty()){
        QJsonObject object;
        object["title"] = text;
        object["completed"] = false; // By default a new To Do is not completed
        EnginioReply *reply = m_model->append(object);
        QObject::connect(reply, &EnginioReply::finished, reply, &EnginioReply::deleteLater);
    }
}

and for removing it:

void MainWindow::removeItem()
{
    QModelIndex index = m_view->currentIndex();
    EnginioReply *reply = m_model->remove(index.row());
    QObject::connect(reply, &EnginioReply::finished, reply, &EnginioReply::deleteLater);
}

Files:

© 2017 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.