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:
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}
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.
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.