At this point you know how you can set up KTutorial so it can be used by the users of your application. However, if you don't provide any tutorial, it will be useless.
As it was already said, tutorials can be made using C++ or a script language. We will begin with C++ tutorials, and later we will dive into scripted tutorials.
So in this section we will see an example showing how a very simple C++ tutorial can be done. There are other ways, but here a Tutorial
derived class approach will be used.
The tutorial will just ask the user if he prefers to write one text or another and then it will wait for the user to clean the text using the Clean action.
First of all, a new C++ class derived from Tutorial
must be done. We will call it SimpleTutorial
. It must include Q_OBJECT macro, as it makes use of Qt's signals and slots mechanism.
As you can see, there are four slots and a private attribute. We will explain latter the purpose of them.
Example 3.4. SimpleTutorial header (C++)
#ifndef SIMPLETUTORIAL_H #define SIMPLETUTORIAL_H #include <ktutorial/Tutorial.h> class SimpleTutorial: public ktutorial::Tutorial { Q_OBJECT public: SimpleTutorial(); public slots: void textSelected(); void numbersSelected(); void textWritten(); void clearTextDone(); private: bool mWriteText; }; #endif
Having seen the header file, from now on we will concentrate on the implementation file (SimpleTutorial.cpp
). This file, as you can suppose, must be added in CMakeLists.txt with the rest of .cpp files to be built when the application is built.
As this is just an example, we will create everything in the constructor instead of subdividing it in smaller methods. Kids, don't do that at home.
As we are deriving Tutorial
class, we must provide a TutorialInformation
in our derived class (we could still pass it as a parameter when our tutorial was created... but it won't have a lot of sense).
So we will call Tutorial
constructor with a null pointer and set the TutorialInformation
in our own constructor.
All the includes and using
declarations necessary for the rest of the example are set now to avoid going back and forth from the constructor body to the includes section.
Example 3.5. Creating a TutorialInformation (C++)
#include <ktutorial/KTutorial.h> #include <ktutorial/Option.h> #include <ktutorial/Step.h> #include <ktutorial/TutorialInformation.h> #include <ktutorial/WaitForSignal.h> //KTutorial classes belong to "ktutorial" namespace using ktutorial::KTutorial; using ktutorial::Option; using ktutorial::Step; using ktutorial::TutorialInformation; using ktutorial::WaitForSignal; SimpleTutorial::SimpleTutorial(): Tutorial(0) { mTutorialInformation = new TutorialInformation("clearText"); mTutorialInformation->setName(i18n("Clear the text area")); mTutorialInformation->setDescription(i18n("This tutorial shows how to clean \ the text area with ease")); //New things will be added here }
Tutorials are composed by several steps that must be taken by the user. Each step contains a text shown to the user when the step is activated. Also, they must have a unique identifier, and the one for the start step must be start .
When designing a tutorial you may want to give the user the option to choose. For example, between alternatives in the path to follow in the tutorial.
Options are added to a step (any step, not just the start step), and when an option is selected by the user, the slot associated to it is called. Here is where the first two slots declared in our header file are used.
Example 3.6. Using options in a step (C++)
//Added after the creation of TutorialInformation //Step start Step* startStep = new Step("start"); startStep->setText(i18n("In this tutorial you will learn how to clear \ text.\nTo do this, first something must be written in the text area. What do \ you prefer to write?")); startStep->addOption(new Option(i18n("Some text")), this, SLOT(textSelected())); startStep->addOption(new Option(i18n("Some numbers")), this, SLOT(numbersSelected())); addStep(startStep); //New things will be added here } void SimpleTutorial::textSelected() { mWriteText = true; nextStep("writeText"); } void SimpleTutorial::numbersSelected() { mWriteText = false; nextStep("writeNumbers"); }
In our start step, when any of the options is selected, its associated slot sets the mWriteText
attribute to know whether the user selected to write some text or some numbers, and it activates the next step depending on the option (the steps must still be added).
But, how can we wait for something to happen in order to activate another step? Using WaitFor
objects. They follow a Composite design pattern and enables you to wait for complex conditions. Refer to the section called “Types of conditions to wait for” section for further information.
We will use just a WaitForSignal
to wait for a textChanged
signal in the text area and check whether the written text is the one expected or not. We can also see in this example how more than one WaitFor
object can be associated with the same slot without any problem. Moreover, using KDE internationalization system we can wait for different strings in different languages, so in English it will wait for "Hello world" while in Spanish it will wait for "Hola mundo".
A question arises. How can our tutorial know about the text area, if it has no reference to it? Of course you can make a pointer to it and set it when creating the tutorial.
But a better approach is name the text area with a descriptive name (like
textArea
) after creating it using setObjectName
method in QObject
, and using findObject
in KTutorial
to get it. This has a drawback, however, as it can only get object children of your main window (at least now; it will be addressed in the future if needed).
Example 3.7. Using WaitFor in a Step (C++)
//Added after the creation of start step //Step write text Step* writeTextStep = new Step("writeText"); writeTextStep->setText(i18n("Write \"%1\" (without quotes) in the text area", i18n("Hello world"))); QObject* textArea = KTutorial::self()->findObject<QObject*>("textArea"); WaitForSignal* waitForTextChanged = new WaitForSignal(textArea, SIGNAL(textChanged())); writeTextStep->addWaitFor(waitForTextChanged, this, SLOT(textWritten())); addStep(writeTextStep); //Step write numbers Step* writeNumbersStep = new Step("writeNumbers"); writeNumbersStep->setText(i18n("Write \"%1\" (without quotes) in the text area", i18n("4 8 15 16 23 42"))); writeNumbersStep->addWaitFor(waitForTextChanged, this, SLOT(textWritten())); addStep(writeNumbersStep); //New things will be added here } ... void SimpleTutorial::textWritten() { KTextEdit* textArea = KTutorial::self()->findObject<KTextEdit*>("textArea"); if ((!mWriteText && textArea->toPlainText() == i18n("4 8 15 16 23 42")) || (mWriteText && textArea->toPlainText() == i18n("Hello world"))) { nextStep("clearText"); } }
And now, just add the two pending steps in the tutorial: one to wait for the text to be cleared, and the end one.
When the text is cleared we only have to change to the end step, but we don't need to do anything else (unlike in the previous cases, where we needed to modify variables or make further checks). In cases like this one, where we only have to change to another step, addWaitFor
and addOption
methods have a special version that gets the id of the step to change to, instead of having to define a method to just change to the next step.
Example 3.8. Finishing the tutorial (C++)
//Step clear text Step* clearTextStep = new Step("clearText"); clearTextStep->setText(i18n("Now, you must trigger the Clear action.\nTo \ do this, you can click in File=>Clear, or in the toolbar button called Clear")); QObject* clearAction = KTutorial::self()->findObject<QObject*>("clear"); clearTextStep->addWaitFor(new WaitForSignal(clearAction, SIGNAL(triggered(bool))), "end"); addStep(clearTextStep); //Step end Step* endStep = new Step("end"); endStep->setText(i18n("And that's all.\nNow you can close the tutorial.")); addStep(endStep); }