J.S. Cruz

Kadio: Slow start

As per my last post, I’m writing a program to listen to internet radio. This will be called Kadio and will live on this GitHub page.

Since I’m still learning Qt, I’ll start with very basic steps. For now, I just want to display text on the screen.

Slow start

The first step is to go through KDE’s tutorial. This is a good starting point to get the environment set up. At the end, we have a very basic skeleton that we can start modifying.

I want to start from the most minimal base possible, so I’ve removed most of the code that the tutorial leaves you. We’ll add all that back later, when we see a need. For now, I just want to have the window divided into two columns, and I want to show a list of strings on the left column. This list should be read from a file. This will serve us as the base for our stations list.

Qt widgets

As per Qt’s documentation, Qt windows are divided into different components:

Qt's main window components
Qt's main window components

KDE’s tutorial leaves us with a text edit widget as the window’s central widget. For a different layout with different widgets, we need to use Qt layout objects.

The main application logic exists in widgets. Layout objects can be attached to widgets (to control their layout). Widgets can also be attached to layouts, in which case the widget becomes a child of the attached layout’s widget, and is subordinate to its layout.

Since we want a two-column, one-row window, looking at the layout classes Qt defines, QBoxLayout seems to be just what we need.

Qt defines QHBoxLayout as a subclass of QBoxLayout as a convenience class for purely horizontal layouts. This is what we’ll use.

For our list of words on the left column, we should use a vertical layout. Similar to QHBoxLayout, we can use QVBoxLayout.

kadio.cpp then becomes:

 1kadio::kadio(QWidget *parent)
 2    : KXmlGuiWindow(parent)
 3{
 4    QWidget* main_window = new QWidget(this);
 5    QHBoxLayout* main_layout = new QHBoxLayout(main_window);
 6
 7    QWidget* left_pane = new QWidget;
 8    QVBoxLayout* left_pane_layout = new QVBoxLayout(left_pane);
 9
10    left_pane_layout->addWidget(new QLabel("one"));
11    left_pane_layout->addWidget(new QLabel("two"));
12    left_pane_layout->addWidget(new QLabel("three"));
13    main_layout->addWidget(left_pane);
14
15    QLabel* right_pane = new QLabel("four");
16    main_layout->addWidget(right_pane);
17
18    this->setCentralWidget(main_window);
19}
Two column display, showing hard-coded lines
Two column display, showing hard-coded lines

main_window has to be a child of this (kadio object). Note that neither left_pane nor right_pane were constructed with main_window as parent; this is done by addWidget when we add those widgets to the main layout.

Why have main_window at all? Why not attach the layout to this and then set this (the kadio object) as the central widget? I’m not sure why, but if you try to attach the layout directly to our object, nothing is shown on-screen. However, this works if the kadio class is not a subclass of KXmlGuiWindow but of, say, QObject. Since we really want to use KXmlGuiWindow (it will be useful later on), we need to add this little layer of indirection.

Variable messages

Adding variable messages is simple enough: we pass a vector of strings to the constructor and loop over them, using each to build a QLabel and attach it to the left column’s layout.

kadio.cpp:

 1kadio::kadio(const QVector<QString>& words, QWidget *parent)
 2    : KXmlGuiWindow(parent)
 3{
 4    QWidget* main_window = new QWidget(this);
 5    QHBoxLayout* main_layout = new QHBoxLayout(main_window);
 6
 7    QWidget* left_pane = new QWidget(this);
 8    QVBoxLayout* left_pane_layout = new QVBoxLayout(left_pane);
 9    for (const QString& w : words) {
10        left_pane_layout->addWidget(new QLabel(w));
11    }
12    main_layout->addWidget(left_pane);
13
14    QLabel* right_pane = new QLabel("four");
15    main_layout->addWidget(right_pane);
16
17    this->setCentralWidget(main_window);
18}

and, in main.cpp:

 1int main(int argc, char *argv[])
 2{
 3    QApplication app(argc, argv);
 4    QVector<QString> words {
 5        "one", "two", "three", "four", "five"
 6    };
 7    kadio* w = new kadio(words);
 8    w->show();
 9    return app.exec();
10}

Reading from a file

We should use Qt’s file API for all file interactions. Opening and closing files are done by QFile directly. We can also use QFiles to read and write data but, since we’re handling text, Qt recommends we use QTextStream, since that gives us properly locale’d QStrings, so that’s what we’ll do.

QFiles aren’t opened on construction – you need to call open(). If we just specify the file mode, Qt assumes we’re opening a binary file, so we need to add QIODevice::Text.

main.cpp:

 1int main(int argc, char *argv[])
 2{
 3    QApplication app(argc, argv);
 4
 5    QFile words_file("test-file");
 6    if (! words_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
 7        return 0;
 8    }
 9
10    QTextStream words_file_stream(&words_file);
11    QVector<QString> words;
12    while (! words_file_stream.atEnd()) {
13        words.append(words_file_stream.readLine());
14    }
15
16    kadio* w = new kadio(words);
17    w->show();
18
19    return app.exec();
20}

Put test-file in the build directory and fill it with whichever sentences you want.

Two column display, with lines read from file
Two column display, with lines read from file

Note that we’re not using the C++ standard library. Although Qt does have some interface compatibility with the standard library, it’s just cleaner to use Qt’s types directly (e.g., Qstring string = stream.readLine() instead of std::string string; std::getline(stream, string)).

Conclusion

That’s it for now. As it is, the program doesn’t do a lot, but we have a graphical display for sentences in a file. That’s not nothing.

In the next post, we’re going to add a media player to the right side of the window, to play audio files listed on the left pane. We also want to be able to change track, so we’re going to have to interact with the left pane. As of now, our git repository looks like this.

Tags: #kde #kadio