Creating a new album from a custom InputDialog

The AlbumListPage needs some data to display. The next step is to be able to add a new album. To do this, at some point we will have to call an AlbumModel function from QML to add this new album. Before building the UI, we have to make a small modification in gallery-core.

The AlbumModel function is already available in QML. However, we cannot directly call AlbumModel::addAlbum(const Album& album) from the QML code; the QML engine will not recognize the function and will throw an error TypeError: Property 'addAlbum' of object AlbumModel(...) is not a function. This can be fixed by simply decorating the desired function with the Q_INVOKABLE macro (as we did for PictureModel::setAlbumId()).

Nonetheless, there is another issue here: Album is a C++ class which is not recognized in QML. If we wanted to have full access to Album in QML, it would involve important modifications to the class:

  • Force Album class to inherit from the QObject class.
  • Add a Q_PROPERTY macro to specify which property of the class should be accessible from QML.
  • Add multiple constructors (copy constructor, QObject* parent, and so on).
  • Force AlbumModel::addAlbum() function to take an Album* rather than an Album&. For complex objects (that is, not primitive types), QML can only handle pointers. This is not a big problem, but using references instead of pointers tends to make the code safer.

These modifications are perfectly reasonable if the class is heavily manipulated in QML. Our use case is very limited: we only want to create a new album. Throughout the application, we will rely on the native Model/View API to display the album data and nothing specific to Album will be used.

For all these reasons, we will simply add a wrapper function in AlbumModel:

// In AlbumModel.h 
... 
QModelIndex addAlbum(const Album& album); 
Q_INVOKABLE void addAlbumFromName(const QString& name); 
... 
 
// In AlbumModel.cpp 
void AlbumModel::addAlbumFromName(const QString& name) 
{ 
    addAlbum(Album(name)); 
} 

The new function addAlbumFromName() just wraps the call to addAlbum() with the desired album name parameter. It can be called from the QML with the Q_INVOKABLE macro.

We can now switch back to the UI in the gallery-mobile project. We will add this album using a QML Dialog. QtQuick provides various default implementations of dialogs:

  • ColorDialog: This dialog is used to choose a color
  • Dialog: This dialog is uses the generic dialog with standard buttons (equivalent of a QDialog)
  • FileDialog: This dialog is used to choose a file from the local filesystem
  • FontDialog: This dialog is used to choose a font
  • MessageDialog: This dialog is used to display a message

You would have expected to see an InputDialog in this list (as we used the QInputDialog widget in Chapter 12Conquering the Desktop UI) but Qt Quick does not have it. Create a new QML File (Qt Quick 2) and name it InputDialog.qml. The content should look like so:

import QtQuick 2.6 
import QtQuick.Layouts 1.3 
import Qt.labs.controls 1.0 
import QtQuick.Dialogs 1.2 
import QtQuick.Window 2.2 
import "." 
 
Dialog { 
 
    property string label: "New item" 
    property string hint: "" 
    property alias editText : editTextItem 
 
    standardButtons: StandardButton.Ok | StandardButton.Cancel 
    onVisibleChanged: { 
        editTextItem.focus = true 
        editTextItem.selectAll() 
    } 
    onButtonClicked: { 
        Qt.inputMethod.hide(); 
    } 
    Rectangle { 
 
        implicitWidth: parent.width 
        implicitHeight: 100 
 
        ColumnLayout { 
            Text { 
                id: labelItem 
                text: label 
                color: Style.text 
            } 
 
            TextInput { 
                id: editTextItem 
                inputMethodHints: Qt.ImhPreferUppercase 
                text: hint 
                color: Style.text 
            } 
        } 
    } 
} 

In this custom InputDialog, we take the generic Qt Quick Dialog and modify it to contain our TextInput item referenced by the ID editTextItem. We also added a labelItem just above editTextItem to describe the expected input. There are several things to note in this dialog.

First, because we want this dialog to be generic, it has to be configurable. The caller should be able to provide parameters to display its specific data. This is done with the three properties at the top of the Dialog element:

  • label: This property configures the displayed text in labelItem.
  • hint: This property is the default text displayed in editTextItem.
  • editText: This property references the "local" editTextItem element. This will let the caller retrieve the value when the dialog is closed.

We also configure the Dialog element to automatically use the platform buttons to validate or cancel the dialog with standardButtons: StandardButton.Ok | StandardButton.Cancel syntax.

Finally, to make the dialog a bit more user-friendly, editTextItem has the focus when the Dialog element becomes visible and the text is selected. These two steps are done in the onVisibleChanged() callback function. When the dialog is hidden (that is, Ok or Cancel has been clicked), we hide the virtual keyboard with Qt.InputMethod.hide().

The InputDialog is ready to be used! Open AlbumListPage.qml and modify it like so:

PageTheme { 
 
    toolbarTitle: "Albums" 
    toolbarButtons: ToolButton { 
        background: Image { 
            source: "qrc:/res/icons/album-add.svg" 
        } 
        onClicked: { 
            newAlbumDialog.open() 
        } 
    } 
 
    InputDialog { 
        id: newAlbumDialog 
        title: "New album" 
        label: "Album name:" 
        hint: "My Album" 
 
        onAccepted: { 
            albumModel.addAlbumFromName(editText.text) 
        } 
    } 

We add InputDialog with the ID newAlbumDialog inside PageTheme element. We define all our custom properties: titlelabel, and hint. When the user clicks on the Ok button, the onAccepted() function is called. Here, it is a simple matter of calling the wrapper function addAlbumFromName() in the AlbumModel element with the entered text.

This Dialog element is not visible by default, we open it by adding a ToolButton in  toolbarButtons. This ToolButton will be added at the far right of the header as we specified in the PageTheme.qml file. To match mobile standards, we simply use a custom icon inside that button rather than text.

Here you can see that it is possible to reference images stored in the .qrc file with the syntax qrc:/res/icons/album-add.svg. We use SVG files to have scalable icons, but you are free to use your own icons for the gallery-mobile application.

When the user clicks on the ToolButton, the onClicked() function is called, where we open newAlbumDialog. On our reference device, a Nexus 5X, this is how it looks:

When the user clicks on the OK button, the whole Model/View pipeline starts to work. This new album is persisted, the AlbumModel element emits the correct signals to notify our ListViewalbumList, to refresh itself. We are starting to leverage the power of our gallery-core, which can be used in a desktop application and a mobile application without rewriting a significant portion of the engine code.