Hello World!

Your first window using KDE Frameworks

Abstract

Your first program shall greet the world with a friendly "Hello World". For that, we will use a KMessageBox and customize one of its buttons.

Preparation

You will need to set up your development environment (so that you can use the KDE Frameworks) first. You can do that in two ways:

  • Go through the setting up your development environment part of the Get Involved documentation. That will give you the necessary development tools and underlying libraries, and build the KDE Frameworks from scratch.
  • Install the KDE Frameworks development packages from your operating system or distribution. The names of these packages, and how to install them, varies per distro, so you will need to investigate on your own.

The Code

Hello World

All the code we need will be in one file, main.cpp. We'll start simple and increment our file as we go further. Create it with the code below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <QApplication>
#include <KMessageBox>

int main (int argc, char *argv[])
{
    QApplication app(argc, argv);

    KGuiItem primaryAction(
        // Content text, Icon
        QStringLiteral("Hello"), QString(),
        // Tooltip text
        QStringLiteral("This is a tooltip"),
        // WhatsThis text
        QStringLiteral("This is a WhatsThis help text."));

    auto messageBox = KMessageBox::questionTwoActions(
        // Parent
        nullptr,
        // MessageBox contents
        QStringLiteral("Hello World"),
        // MessageBox title
        QStringLiteral("Hello Title"),
        // Primary action, Secondary action
        primaryAction, KStandardGuiItem::cancel());

    if (messageBox == KMessageBox::PrimaryAction)
    {
        return EXIT_SUCCESS;
    }
    else
    {
        return EXIT_FAILURE;
    }
}

Our popup box is a KMessageBox , which has primarily two buttons: a PrimaryAction , which usually serves as a confirmation button, and a SecondaryAction , which usually portrays a different action, like a cancel or discard button. The popup box uses the KMessageBox class, the primary action uses a custom KGuiItem with the text "Hello", and the secondary action uses KStandardGuiItem::cancel() .

First we need to create a QApplication object. It needs to be created exactly once and before any other KDE Frameworks or Qt object, as it is the starting point for creating your application and thus required for other components, like Ki18n for translations.

The first argument of the KGuiItem constructor is the text that will appear on the item (in our case, a button object to be used soon). Then we have the option to set an icon for the button, but for now we don't want one so we can pass an empty QString using QString(). We then set the tooltip (the text that appears when you hover over an item), and finally the "What's This?" text (accessed through right-clicking or Shift-F1).

Now that we have the item needed for our primary action button, we can create our popup with KMessageBox::questionTwoActions() . The first argument is the parent widget of the KMessageBox , which is not needed for us here, so we pass nullptr. The second argument is the text that will appear inside the message box and above the buttons, in our case, "Hello World". The third is the caption shown in the window's titlebar, "Hello Title". Then, we set our custom KGuiItem , primaryAction. Lastly, we add a convenience object with KStandardGuiItem::cancel() , which returns a ready-made KGuiItem with localized text and cancel functionality, satisfying the function signature.

About and Internationalization

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <QApplication>
#include <KMessageBox>
#include <KAboutData>
#include <KLocalizedString>

int main (int argc, char *argv[])
{
    using namespace Qt::Literals::StringLiterals;

    QApplication app(argc, argv);
    KLocalizedString::setApplicationDomain("tutorial1");
    
    KAboutData aboutData(
        // The program name used internally. (componentName)
        u"tutorial1"_s,
        // A displayable program name string. (displayName)
        i18n("Tutorial 1"),
        // The program version string. (version)
        u"1.0"_s,
        // Short description of what the app does. (shortDescription)
        i18n("Displays a KMessageBox popup"),
        // The license this code is released under
        KAboutLicense::GPL,
        // Copyright Statement (copyrightStatement = QString())
        i18n("(c) 2021"),
        // Optional text shown in the About box.
        // Can contain any information desired. (otherText)
        i18n("Some text..."),
        // The program homepage string. (homePageAddress = QString())
        u"https://example.com/"_s,
        // The bug report email address
        // (bugsEmailAddress = QLatin1String("submit@bugs.kde.org")
        u"submit@bugs.kde.org"_s);

    aboutData.addAuthor(
        i18n("Name"),
        i18n("Author Role"),
        u"your@email.com"_s,
        u"https://your.website.com"_s,
        u"OCS Username"_s);

    KAboutData::setApplicationData(aboutData);
    
    KGuiItem primaryAction(
        i18n("Hello"), QString(),
        i18n("This is a tooltip"),
        i18n("This is a WhatsThis help text."));

    auto messageBox = KMessageBox::questionTwoActions(
        nullptr,
        i18n("Hello World"),
        i18n("Hello Title"),
        primaryAction, KStandardGuiItem::cancel());

    if (messageBox == KMessageBox::PrimaryAction)
    {
        return EXIT_SUCCESS;
    }
    else
    {
        return EXIT_FAILURE;
    }
}

For your application to be localized, we must first prepare our code so that it can be adapted to various languages and regions without engineering changes: this process is called internationalization. KDE uses Ki18n for that, which provides KLocalizedString .

We start with a call to KLocalizedString::setApplicationDomain() , which is required to properly set the translation catalog and must be done before everything else (except the creation of the QApplication instance). After that, we can just start enveloping the relevant user-visible, translatable strings with i18n(). The non-user visible strings that do not need to be translated should use a QStringLiteral instead. We'll use those next with KAboutData .

More information on internalization can be found in the programmer's guide for internationalization.

KAboutData is a core KDE Frameworks component that stores information about an application, which can then be reused by many other KDE Frameworks components. We instantiate a new KAboutData object with its fairly complete default constructor and add author information. After all the required information has been set, we call KAboutData::setApplicationData() to initialize the properties of the QApplication object.

Note how the message box adapts to its own contents, and how the window title now includes "Tutorial 1", like we set using KAboutData . This property can then be accessed with KAboutData::displayName() when needed.

One more thing of note is that, if you are using a different system language, the KStandardGuiItem::cancel() button we created will likely already show up in your language instead of saying "Cancel".

Command line

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <QApplication>
#include <KMessageBox>
#include <KAboutData>
#include <KLocalizedString>
#include <QCommandLineParser>

int main (int argc, char *argv[])
{
    using namespace Qt::Literals::StringLiterals;

    QApplication app(argc, argv);
    KLocalizedString::setApplicationDomain("tutorial1");
    
    KAboutData aboutData(
        u"tutorial1"_s,
        i18n("Tutorial 1"),
        u"1.0"_s,
        i18n("Displays a KMessageBox popup"),
        KAboutLicense::GPL,
        i18n("(c) 2021"),
        i18n("Some text..."),
        u"https://example.com/"_s,
        u"submit@bugs.kde.org"_s);

    aboutData.addAuthor(
        i18n("Name"),
        i18n("Author Role"),
        u"your@email.com"_s,
        u"https://your.website.com"_s,
        u"OCS Username"_s);

    KAboutData::setApplicationData(aboutData);

    // New section
    QCommandLineParser parser;
    aboutData.setupCommandLine(&parser);
    parser.process(app);
    aboutData.processCommandLine(&parser);
    
    KGuiItem primaryAction(
        i18n("Hello"), QString(),
        i18n("This is a tooltip"),
        i18n("This is a WhatsThis help text."));

    auto messageBox = KMessageBox::questionTwoActions(
        nullptr,
        i18n("Hello World\n"
             "This messagebox was made with KDE Frameworks."),
        i18n("Hello Title"),
        primaryAction, KStandardGuiItem::cancel());

    if (messageBox == KMessageBox::PrimaryAction)
    {
        return EXIT_SUCCESS;
    }
    else
    {
        return EXIT_FAILURE;
    }
}

Then we come to QCommandLineParser . This is the class one would use to specify command line flags to open your program with a specific file, for instance. However, in this tutorial, we simply initialize it with the KAboutData object we created before so we can use the --version or --author flags that are provided by default by Qt.

We're all done as far as the code is concerned. Now to build it and try it out.

Build

In order to run our project, we need a build system in place to compile and link the required libraries; for that, we use the industry standard CMake, together with files in our project folder called CMakeLists.txt. CMake uses this file to run mainly three steps: configure, build, and install. During the configure step it will generate a Makefile (if using make) or a build.ninja (if using ninja), which is then used to build.

You can learn more about why KDE uses CMake in this article from Alexander Neundorf.

The Qt Company provides a good tutorial for using CMake with Qt.

The CMake website also provides a tutorial from scratch with examples.

And KDAB provides a YouTube playlist explaining CMake.

CMakeLists.txt

Create a file named CMakeLists.txt in the same directory as main.cpp with this content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
cmake_minimum_required(VERSION 3.20)

project (helloworld)

set(QT_MIN_VERSION "6.6.0")
set(KF_MIN_VERSION "6.0.0")

find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)

include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(FeatureSummary)

find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS
    Core    # QCommandLineParser, QStringLiteral
    Widgets # QApplication
)

find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS
    CoreAddons      # KAboutData
    I18n            # KLocalizedString
    WidgetsAddons   # KMessageBox
)

add_executable(helloworld)

target_sources(helloworld
    PRIVATE
    main.cpp
)

target_link_libraries(helloworld
    Qt6::Widgets
    KF6::CoreAddons
    KF6::I18n
    KF6::WidgetsAddons
)

install(TARGETS helloworld ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})

feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)

The find_package() function locates the package that you ask it for (in this case ECM, Qt5, or KF5) and sets some variables describing the location of the package's headers and libraries. ECM, or Extra CMake Modules, is required to import special CMake files and functions for building KDE applications.

Here we try to find the modules for Qt 5 and KDE Frameworks 5 required to build our tutorial. The necessary files are included by CMake so that the compiler can see them at build time. Minimum version numbers are set at the very top of CMakeLists.txt file for easier reference.

Next we create a variable called helloworld_SRCS using the set() function. In this case we simply set it to the name of our only source file.

Then we use add_executable() to create an executable called helloworld from the source files listed in our helloworld_SRCS variable. Afterwards, we link our executable to the necessary libraries using the target_link_libraries() function. The install() function call creates a default "install" target, putting executables and libraries in the default path using a convenience macro provided by ECM. Additionally, just by including ECM, an "uninstall" target automatically gets created based on this "install" target.

Running our application

To compile, link and install your program, you must have the following software installed: cmake, make or ninja, and gcc-c++/g++, and the Qt 6 and KDE Frameworks development packages. To be sure you have everything, follow this install guide.

First we configure our project inside of a build/ folder:

cmake -B build/
# Alternatively, with ninja instead of make:
cmake -B build/ -G Ninja

Then we compile the project inside the same build/ folder:

cmake --build build/

And launch it with:

./build/helloworld

You can also run the binary with flags. The flag --help is a standard flag added by Qt via QCommandLineParser , and the content of the --version, --author and --license flags should match the information we added with KAboutData .

./build/bin/helloworld --help
./build/bin/helloworld --version
./build/bin/helloworld --author
./build/bin/helloworld --license