From: Enar Väikene Date: Mon, 26 Sep 2011 10:16:45 +0000 (+0300) Subject: Finalized tutorial files. X-Git-Url: https://vaikene.ee/gitweb/index.html?a=commitdiff_plain;h=30c0930d760d6338dba3982e01125b10f84c47b2;p=evaf Finalized tutorial files. --- diff --git a/www/pswgen09.html b/www/pswgen09.html new file mode 100644 index 0000000..7f05aba --- /dev/null +++ b/www/pswgen09.html @@ -0,0 +1,217 @@ + + + + + + eVaf Tutorial - 09 - GUI Module + + + + + + + + + +

eVaf Tutorial

+ +

09 - GUI Module

+ +

The Graphical User Interface (GUI) module implements the main window of the application. It uses previously + created Generator and Storage modules to generate and store passwords.

+ +

We start by creating the gui.h file in the src/apps/PswGen/GUI directory:

+ +
/**
+ * @file PswGen/GUI/gui.h
+ */
+
+#ifndef __PSWGEN_GUI_GUI_H
+#  define __PSWGEN_GUI_GUI_H
+
+#endif // gui.h
+ +

Include the Plugins/iPlugin header file as this is going to be a plugin and also QObject and + QString as these are almost always needed:

+ +
#include <Plugins/iPlugin>
+#include <QObject>
+#include <QString>
+ +

We use the eVaf::PswGen::GUI namespace for this module:

+ +
namespace eVaf {
+namespace PswGen {
+namespace GUI {
+
+} // namespace eVaf::PswGen::GUI
+} // namespace eVaf::PswGen
+} // namespace eVaf
+ +

The module itself can be implemented as one single class derived from the Plugins::iPlugin parent class:

+ +
class Module : public Plugins::iPlugin
+{
+    Q_OBJECT
+
+public:
+
+    Module();
+
+    virtual ~Module();
+
+};
+ +

Implement pure virtual methods derived from the parent class. The main GUI window is created in the init() method + and the module can only be ready when the initialization is completed. We use the mReady flag to indicate this.

+ +
    virtual bool init(const QString & args);
+
+    virtual void done();
+
+    virtual bool isReady() const { return mReady; }
+
+private:
+
+    bool mReady;
+ +

In the private members section we store interfaces from previously written modules:

+ +
+    eVaf::PswGen::iGenerator * mGenerator;
+
+    eVaf::PswGen::iStorage * mStorage;
+ +

Since these are not known yet, we also need to add forward declarations. These go into the eVaf::PswGen namespace + at the beginning of the file:

+ +
+namespace eVaf {
+namespace PswGen {
+    struct iGenerator;
+    struct iStorage;
+ +

The main window needs couple of widgets on it:

+ + + +
+    /// Widgets on the screen
+    QLineEdit * wMasterPassword;
+    QLineEdit * wName;
+    QSpinBox * wLength;
+    QLineEdit * wPassword;
+    QPushButton * wGenerate;
+    QPushButton * wCopy;
+ +

Add slots for push buttons:

+ +
private slots:
+
+    /// 'Generate' button clicked
+    void generateClicked();
+
+    /// 'Copy' button clicked
+    void copyClicked();
+ +

The private keyword is ignored by the Qt meta-object compiler, but it is a good practice to have it here + as an indicator, that these slots are private and should not be used outside of the class.

+ +

The Generate push button should be enabled only when the master password and name are given. Add another + slot that will be connected to the textChanged() signal from these widgets:

+ +
+    /// Master password or name changed
+    void textChanged(QString const &);
+ +

And here is the final gui.h file:

+ +
/**
+ * @file PswGen/GUI/gui.h
+ */
+#ifndef __PSWGEN_GUI_GUI_H
+#  define __PSWGEN_GUI_GUI_H
+
+#include <Plugins/iPlugin>
+
+#include <QObject>
+#include <QString>
+
+class QLineEdit;
+class QSpinBox;
+class QPushButton;
+
+namespace eVaf {
+namespace PswGen {
+    struct iGenerator;
+    struct iStorage;
+namespace GUI {
+
+/// Graphical User Interface for the PswGen application.
+class Module : public Plugins::iPlugin
+{
+    Q_OBJECT
+
+public:
+
+    Module();
+
+    virtual ~Module();
+
+    virtual bool init(const QString & args);
+
+    virtual void done();
+
+    virtual bool isReady() const { return mReady; }
+
+private slots:
+
+    /// Master password or name changed
+    void textChanged(QString const &);
+
+    /// 'Generate' button clicked
+    void generateClicked();
+
+    /// 'Copy' button clicked
+    void copyClicked();
+
+private: // Members
+
+    static int const DefaultPasswordLength;
+
+    /// Flag indicating that the module is ready
+    bool mReady;
+
+    /// The iGenerator interface
+    eVaf::PswGen::iGenerator * mGenerator;
+
+    /// The iStorage interface (can be null)
+    eVaf::PswGen::iStorage * mStorage;
+
+    /// Widgets on the screen
+    QLineEdit * wMasterPassword;
+    QLineEdit * wName;
+    QSpinBox * wLength;
+    QLineEdit * wPassword;
+    QPushButton * wGenerate;
+    QPushButton * wCopy;
+};
+
+} // namespace eVaf::PswGen::GUI
+} // namespace eVaf::PswGen
+} // namespace eVaf
+
+#endif // gui.h
+
+ +

Continue implementing the GUI Module.

+ + + diff --git a/www/pswgen10.html b/www/pswgen10.html new file mode 100644 index 0000000..f1ea8e1 --- /dev/null +++ b/www/pswgen10.html @@ -0,0 +1,472 @@ + + + + + + eVaf Tutorial - 10 - GUI Module + + + + + + + + + +

eVaf Tutorial

+ +

10 - GUI Module

+ +

Create the gui.cpp file in the src/apps/PswGen/GUI directory:

+ +
/**
+ * @file PswGen/GUI/gui.cpp
+ */
+
+#include "gui.h"
+
+#include <QtGui>
+ +

Copy version information files from the Storage module:

+ +
evaf/src/apps/PswGen/GUI $ cp ../Storage/version.{h,rc} .
+ +

Modify the version.h file:

+ +
/**
+ * @file PswGen/GUI/version.h
+ */
+
+#ifndef __PSWGEN_GUI_VERSION_H
+#  define __PSWGEN_GUI_VERSION_H
+
+#include <version_rc.h>
+
+/**
+ * Module/library version number in the form major,minor,release,build
+ */
+#define VER_FILE_VERSION                0,1,1,1
+
+/**
+ * Module/library version number in the string format (shall end with \0)
+ */
+#define VER_FILE_VERSION_STR            "0.1.1.2\0"
+
+/**
+ * Module/library name (shall end with \0)
+ */
+#define VER_MODULE_NAME_STR             "PswGui\0"
+
+/**
+ * Module type (see version_rc.h for all the types)
+ */
+#define VER_MODULE_TYPE                 MT_GENERIC
+
+/**
+ * Module type in the string format (see version_rc for all the types)
+ */
+#define VER_MODULE_TYPE_STR             MT_GENERIC
+
+/**
+ * Original file name for windows (shall end with \0)
+ */
+#define VER_ORIGINAL_FILE_NAME_STR      "PswGui.dll\0"
+
+/**
+ * Description of the module/library (shall end with \0)
+ */
+#define VER_FILE_DESCRIPTION_STR         "User interface for the PswGen application.\0"
+
+#endif // version.h
+ +

Include the version.h header file in the gui.cpp file and export version information:

+ +
#include "version.h"
+VER_EXPORT_VERSION_INFO()
+ +

Use the Q_EXPORT_PLUGIN2() macro to make it a Qt plugin:

+ +
Q_EXPORT_PLUGIN2(VER_MODULE_NAME_STR, eVaf::PswGen::GUI::Module)
+ +

The main job of setting up the user interface is done in the init() function and our constructor and destructor are + very simple:

+ +
Module::Module()
+    : Plugins::iPlugin()
+    , mReady(false)
+    , mGenerator(0)
+    , mStorage(0)
+{
+    setObjectName(QString("%1.%2").arg(VER_MODULE_NAME_STR).arg(__FUNCTION__));
+
+    EVAF_INFO("%s created", qPrintable(objectName()));
+}
+
+Module::~Module()
+{
+    EVAF_INFO("%s destroyed", qPrintable(objectName()));
+}
+ +

The init() function:

+ +
    +
  1. Queries and stores interfaces implemented in other modules. The iGenerator interface must be implemented; + the iStorage interface is optional and we can work without it.
  2. +
  3. Gets the main window interface iSdiWindow.
  4. +
  5. Creates the layout for the main window widget and adds our widgets to the layout.
  6. +
  7. Connects signals on widgets to slots in this class.
  8. +
  9. Uses actions QAction to add keyboard shortcuts -- Enter key generates a password and Esc key + quits the application.
  10. +
  11. Sets the mReady flag to true when all this is done.
  12. +
+ +
bool Module::init(QString const & args)
+{
+    Q_UNUSED(args);
+
+    // Get the iGenerator interface
+    EVAF_TEST_X((mGenerator = evafQueryInterface<PswGen::iGenerator>("iGenerator")), "No iGenerator interface");
+
+    // Get the iStorage interface (can be null)
+    mStorage = evafQueryInterface<PswGen::iStorage>("iStorage");
+    if (!mStorage)
+        EVAF_WARNING("No iStorage interface");
+
+    // Get the main window interface and fill it with widgets
+    SdiWindow::iSdiWindow * win = evafQueryInterface<SdiWindow::iSdiWindow>("iSdiWindow");
+    EVAF_TEST_X(win, "No iSdiWindow interface");
+
+    QVBoxLayout * v = new QVBoxLayout;
+    win->widget()->setLayout(v);
+
+    QGridLayout * g = new QGridLayout;
+    v->addLayout(g);
+    g->setColumnStretch(2, 2);
+
+    QLabel * l = new QLabel(tr("Master &password:", VER_MODULE_NAME_STR));
+    l->setAlignment(Qt::AlignRight);
+    g->addWidget(l, 0, 0);
+
+    wMasterPassword = new QLineEdit;
+    l->setBuddy(wMasterPassword);
+    connect(wMasterPassword, SIGNAL(textChanged(QString)), this, SLOT(textChanged(QString)));
+    wMasterPassword->setEchoMode(QLineEdit::Password);
+    g->addWidget(wMasterPassword, 0, 1, 1, 2);
+
+    l = new QLabel(tr("Web site or application &name:", VER_MODULE_NAME_STR));
+    l->setAlignment(Qt::AlignRight);
+    g->addWidget(l, 1, 0);
+
+    wName = new QLineEdit;
+    l->setBuddy(wName);
+    if (mStorage) {
+        QCompleter * completer = new QCompleter(wName);
+        completer->setModel(mStorage->autoCompletionModel());
+        completer->setCompletionMode(QCompleter::InlineCompletion);
+        wName->setCompleter(completer);
+    }
+    connect(wName, SIGNAL(textChanged(QString)), this, SLOT(textChanged(QString)));
+    g->addWidget(wName, 1, 1, 1, 2);
+    win->widget()->setFocusProxy(wName);
+
+    l = new QLabel(tr("&Length of the password:", VER_MODULE_NAME_STR));
+    l->setAlignment(Qt::AlignRight);
+    g->addWidget(l, 2, 0);
+
+    wLength = new QSpinBox;
+    l->setBuddy(wLength);
+    wLength->setRange(0, mGenerator->maxLength());
+    wLength->setValue(DefaultPasswordLength);
+    wLength->setSpecialValueText(tr("Maximum", VER_MODULE_NAME_STR));
+    g->addWidget(wLength, 2, 1);
+
+    l = new QLabel(tr("Password:"));
+    l->setAlignment(Qt::AlignRight);
+    g->addWidget(l, 3, 0);
+
+    wPassword = new QLineEdit;
+    wPassword->setReadOnly(true);
+    g->addWidget(wPassword, 3, 1, 1, 2);
+
+    v->addStretch();
+
+    QHBoxLayout * h = new QHBoxLayout;
+    h->addStretch();
+    v->addLayout(h);
+
+    wGenerate = new QPushButton(tr("&Generate", VER_MODULE_NAME_STR));
+    wGenerate->setDisabled(true);
+    wGenerate->setDefault(true);
+    connect(wGenerate, SIGNAL(clicked()), this, SLOT(generateClicked()));
+    h->addWidget(wGenerate);
+
+    wCopy = new QPushButton(tr("&Copy to Clipboard", VER_MODULE_NAME_STR));
+    wCopy->setDisabled(true);
+    connect(wCopy, SIGNAL(clicked()), this, SLOT(copyClicked()));
+    h->addWidget(wCopy);
+
+    QAction * a = new QAction(win->widget());
+    a->setShortcut(Qt::Key_Return);
+    connect(a, SIGNAL(triggered()), this, SLOT(generateClicked()));
+    win->widget()->addAction(a);
+
+    a = new QAction(win->widget());
+    a->setShortcut(Qt::Key_Escape);
+    connect(a, SIGNAL(triggered()), qApp, SLOT(quit()));
+    win->widget()->addAction(a);
+
+    mReady = true;
+
+    EVAF_INFO("%s initialized", qPrintable(objectName()));
+
+    return true;
+}
+ +

The done() function simply sets the mReady flag back to false:

+ +
void Module::done()
+{
+    mReady = false;
+
+    EVAF_INFO("%s finalized", qPrintable(objectName()));
+}
+ +

The Generate push button is disabled by default. We connected textChanged() signals to the textChanged() slot, where we enable the Generate push button if master password and name fields are not empty.

+ +

We can also query for stored passwords and update fields on the window with data from the storage. The iStorage::query() + method can return an empty shared data pointer and we have to check for it before using fields in the shared data object.

+ +
void Module::textChanged(QString const &)
+{
+    wGenerate->setDisabled(wMasterPassword->text().isEmpty() || wName->text().isEmpty());
+    if (!wName->text().isEmpty() && mStorage) {
+        QExplicitlySharedDataPointer<PswGen::Storage::Data> data = mStorage->query(wName->text());
+        if (data)
+            wLength->setValue(data->length());
+    }
+}
+ +

The generateClicked() slot is connected to the Generate push button and also to the Enter key + action. The push button is disabled if master password or name fields are empty, but not the Enter key action and + we need to check for it once more.

+ +

Then we generate the password using the iGenerator::generatePassword() method and show it on the window. We also + enable the Copy to Clipboard push button.

+ +

If we have the iStorage, we either store the new data record or update an existing one in the storage.

+ +
void Module::generateClicked()
+{
+    if (wMasterPassword->text().isEmpty() || wName->text().isEmpty())
+        return;
+    wPassword->setText(mGenerator->generatePassword(wName->text(), wMasterPassword->text(), wLength->value()));
+    wCopy->setEnabled(true);
+    if (mStorage) {
+        QExplicitlySharedDataPointer<PswGen::Storage::Data> data = mStorage->query(wName->text());
+        if (!data)
+            data = new Storage::Data(wName->text(), wLength->value());
+        else
+            data->setLength(wLength->value());
+        mStorage->save(wName->text(), data);
+    }
+}
+ +

The final method in our module is the copyClicked() slot, which simply copies anything from the generated password + field to the clipboard:

+ +
void Module::copyClicked()
+{
+    QClipboard * clipboard = QApplication::clipboard();
+    if (clipboard)
+        clipboard->setText(wPassword->text());
+}
+ +

Here is the final gui.cpp file:

+ +
/**
+ * @file PswGen/GUI/gui.cpp
+ */
+
+#include "gui.h"
+#include "version.h"
+
+#include "Generator/iGenerator"
+#include "Storage/iStorage"
+
+#include <Common/Globals>
+#include <Common/iLogger>
+#include <Common/iRegistry>
+#include <SdiWindow/iSdiWindow>
+
+#include <QtGui>
+
+VER_EXPORT_VERSION_INFO()
+Q_EXPORT_PLUGIN2(VER_MODULE_NAME_STR, eVaf::PswGen::GUI::Module)
+
+using namespace eVaf;
+using namespace eVaf::PswGen::GUI;
+
+int const Module::DefaultPasswordLength = 16;
+
+Module::Module()
+    : Plugins::iPlugin()
+    , mReady(false)
+    , mGenerator(0)
+    , mStorage(0)
+{
+    setObjectName(QString("%1.%2").arg(VER_MODULE_NAME_STR).arg(__FUNCTION__));
+
+    EVAF_INFO("%s created", qPrintable(objectName()));
+}
+
+Module::~Module()
+{
+    EVAF_INFO("%s destroyed", qPrintable(objectName()));
+}
+
+bool Module::init(QString const & args)
+{
+    Q_UNUSED(args);
+
+    // Get the iGenerator interface
+    EVAF_TEST_X((mGenerator = evafQueryInterface<PswGen::iGenerator>("iGenerator")), "No iGenerator interface");
+
+    // Get the iStorage interface (can be null)
+    mStorage = evafQueryInterface<PswGen::iStorage>("iStorage");
+    if (!mStorage)
+        EVAF_WARNING("No iStorage interface");
+
+    // Get the main window interface and fill it with the widgets
+    SdiWindow::iSdiWindow * win = evafQueryInterface<SdiWindow::iSdiWindow>("iSdiWindow");
+    EVAF_TEST_X(win, "No iSdiWindow interface");
+
+    QVBoxLayout * v = new QVBoxLayout;
+    win->widget()->setLayout(v);
+
+    QGridLayout * g = new QGridLayout;
+    v->addLayout(g);
+    g->setColumnStretch(2, 2);
+
+    QLabel * l = new QLabel(tr("Master &password:", VER_MODULE_NAME_STR));
+    l->setAlignment(Qt::AlignRight);
+    g->addWidget(l, 0, 0);
+
+    wMasterPassword = new QLineEdit;
+    l->setBuddy(wMasterPassword);
+    connect(wMasterPassword, SIGNAL(textChanged(QString)), this, SLOT(textChanged(QString)));
+    wMasterPassword->setEchoMode(QLineEdit::Password);
+    g->addWidget(wMasterPassword, 0, 1, 1, 2);
+
+    l = new QLabel(tr("Web site or application &name:", VER_MODULE_NAME_STR));
+    l->setAlignment(Qt::AlignRight);
+    g->addWidget(l, 1, 0);
+
+    wName = new QLineEdit;
+    l->setBuddy(wName);
+    if (mStorage) {
+        QCompleter * completer = new QCompleter(wName);
+        completer->setModel(mStorage->autoCompletionModel());
+        completer->setCompletionMode(QCompleter::InlineCompletion);
+        wName->setCompleter(completer);
+    }
+    connect(wName, SIGNAL(textChanged(QString)), this, SLOT(textChanged(QString)));
+    g->addWidget(wName, 1, 1, 1, 2);
+    win->widget()->setFocusProxy(wName);
+
+    l = new QLabel(tr("&Length of the password:", VER_MODULE_NAME_STR));
+    l->setAlignment(Qt::AlignRight);
+    g->addWidget(l, 2, 0);
+
+    wLength = new QSpinBox;
+    l->setBuddy(wLength);
+    wLength->setRange(0, mGenerator->maxLength());
+    wLength->setValue(DefaultPasswordLength);
+    wLength->setSpecialValueText(tr("Maximum", VER_MODULE_NAME_STR));
+    g->addWidget(wLength, 2, 1);
+
+    l = new QLabel(tr("Password:"));
+    l->setAlignment(Qt::AlignRight);
+    g->addWidget(l, 3, 0);
+
+    wPassword = new QLineEdit;
+    wPassword->setReadOnly(true);
+    g->addWidget(wPassword, 3, 1, 1, 2);
+
+    v->addStretch();
+
+    QHBoxLayout * h = new QHBoxLayout;
+    h->addStretch();
+    v->addLayout(h);
+
+    wGenerate = new QPushButton(tr("&Generate", VER_MODULE_NAME_STR));
+    wGenerate->setDisabled(true);
+    wGenerate->setDefault(true);
+    connect(wGenerate, SIGNAL(clicked()), this, SLOT(generateClicked()));
+    h->addWidget(wGenerate);
+
+    wCopy = new QPushButton(tr("&Copy to Clipboard", VER_MODULE_NAME_STR));
+    wCopy->setDisabled(true);
+    connect(wCopy, SIGNAL(clicked()), this, SLOT(copyClicked()));
+    h->addWidget(wCopy);
+
+    QAction * a = new QAction(win->widget());
+    a->setShortcut(Qt::Key_Return);
+    connect(a, SIGNAL(triggered()), this, SLOT(generateClicked()));
+    win->widget()->addAction(a);
+
+    a = new QAction(win->widget());
+    a->setShortcut(Qt::Key_Escape);
+    connect(a, SIGNAL(triggered()), qApp, SLOT(quit()));
+    win->widget()->addAction(a);
+
+    mReady = true;
+
+    EVAF_INFO("%s initialized", qPrintable(objectName()));
+
+    return true;
+}
+
+void Module::done()
+{
+    mReady = false;
+
+    EVAF_INFO("%s finalized", qPrintable(objectName()));
+}
+
+void Module::textChanged(QString const &)
+{
+    wGenerate->setDisabled(wMasterPassword->text().isEmpty() || wName->text().isEmpty());
+    if (!wName->text().isEmpty() && mStorage) {
+        QExplicitlySharedDataPointer<PswGen::Storage::Data> data = mStorage->query(wName->text());
+        if (data)
+            wLength->setValue(data->length());
+    }
+}
+
+void Module::generateClicked()
+{
+    if (wMasterPassword->text().isEmpty() || wName->text().isEmpty())
+        return;
+    wPassword->setText(mGenerator->generatePassword(wName->text(), wMasterPassword->text(), wLength->value()));
+    wCopy->setEnabled(true);
+    if (mStorage) {
+        QExplicitlySharedDataPointer<PswGen::Storage::Data> data = mStorage->query(wName->text());
+        if (!data)
+            data = new Storage::Data(wName->text(), wLength->value());
+        else
+            data->setLength(wLength->value());
+        mStorage->save(wName->text(), data);
+    }
+}
+
+void Module::copyClicked()
+{
+    QClipboard * clipboard = QApplication::clipboard();
+    if (clipboard)
+        clipboard->setText(wPassword->text());
+}
+ +

Build the PswGen application.

+ + + diff --git a/www/pswgen11.html b/www/pswgen11.html new file mode 100644 index 0000000..7fb3f32 --- /dev/null +++ b/www/pswgen11.html @@ -0,0 +1,198 @@ + + + + + + eVaf Tutorial - 11 - Building PswGen application + + + + + + + + + +

eVaf Tutorial

+ +

11 - Buiding PswGen application

+ +

CMakeLists.txt

+ +

Copy an existing CMakeLists.txt file from the Storage module:

+ +
evaf/src/apps/PswGen/GUI $ cp ../Storage/CMakeLists.txt .
+ +

We need to modify the TARGET variable and remove QT_USE_QTSQL and QT_DONT_USE_QTGUI variables. + This module needs QtGui and does not need QtSql. Als add SdiWindow to the list of eVaf libraries + as the module that implements the main window.

+ +
# Name of the target
+set(TARGET PswGui)
+
+# Qt modules
+include(${QT_USE_FILE})
+
+# Required eVaf libraries
+set(eVaf_LIBRARIES CommonLib PluginsLib SdiWindow)
+ +

Here is the final CMakeLists.txt file:

+ +
# src/apps/PswGen/GUI/CMakeLists.txt
+
+# Name of the target
+set(TARGET PswGui)
+
+# Qt modules
+include(${QT_USE_FILE})
+
+# Include directories
+include_directories(${eVaf_INCLUDE})
+
+# Required eVaf libraries
+set(eVaf_LIBRARIES CommonLib PluginsLib SdiWindow)
+
+# Source files
+set(SRCS
+    gui.cpp
+)
+
+# Header files for the Qt meta-object compiler
+set(MOC_HDRS
+    gui.h
+)
+
+# Version info resource file for Windows builds
+if(WIN32)
+    set(SRCS ${SRCS} version.rc)
+endif(WIN32)
+
+# Run the Qt meta-object compiler
+qt4_wrap_cpp(MOC_SRCS ${MOC_HDRS})
+
+# Compile the module
+add_library(${TARGET} SHARED ${SRCS} ${MOC_SRCS})
+
+# Link the module
+target_link_libraries(${TARGET} ${QT_LIBRARIES} ${eVaf_LIBRARIES})
+ +

Open the CMakeLists.txt file in the parent directory and add the command to include the GUI + sub-directory:

+ +
# src/apps/PswGen/CMakeLists.txt
+# ...
+add_subdirectory(GUI)
+ +

Building the module

+ +

Go to the previously made build directory and build the module:

+ +
evaf $ cd build
+evaf/build $ make PswGui
+ +

Check the bin directory, which should now contain two new libraries:

+ +
evaf/build $ ls bin
+libCommonLib.so*  libPluginsLib.so*  libPswGen.so*  libPswGui.so*  libPswStorage.so*  libSdiWindow.so*
+evaf/build $
+ +

The libSdiWindow.so library was built because it is a dependency of the GUI module.

+ +

Running the PswGen application

+ +

In the same build directory, create few more sub-directories needed by any eVaf applications:

+ +
 evaf/build $ mkdir {etc,log}
+ +

Create the PswGen.xml file in the build/etc directory. The PswGen.xml file defines the + application by specifying all the modules that the application needs to load.

+ +
<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE eVaf>
+<eVaf version="1.0">
+    <plugins>
+        <plugin name="SdiWindow" filename="SdiWindow" />
+        <plugin name="Generator" filename="PswGen" />
+        <plugin name="Storage" filename="PswStorage" />
+        <plugin name="GUI" filename="PswGui" />
+    </plugins>
+</eVaf>
+ +

Modules are loaded and initialized in the order how they are specified in the xml file. We have to make sure that + SdiWindow, Generator and Storage modules are loaded before the GUI module. Otherwise + their interfaces wouldn't be available when the GUI module is initialized.

+ +

The name attribute here is optional and will be replaced with the name reported by the actual module.

+ +

The filename attribute specifies the name of the library file without prefixes and suffixes meaning that + libPswGen.so shall be specified as PswGen. This makes sure that the same xml file can be also used + on Windows, where the library would be called PswGen.dll.

+ +

There is no executable file yet that we can run. Build it with the following command:

+ +
evaf/build $ make eVafGUI
+ +

This command builds the GUI executable and stores it in the evaf/build/bin directory. The bin directory should look now the following:

+ +
evaf/build $ ls bin
+eVafGUI*  libCommonLib.so*  libPluginsLib.so*  libPswGen.so*  libPswGui.so*  libPswStorage.so*  libSdiWindow.so*
+evaf/build $
+ +

Now we can run the PswGen application. Change --dataroot=${HOME}/evaf/build to your actual build directory name:

+ +
evaf/build $ bin/eVafGUI --application=PswGen --dataroot=${HOME}/evaf/build --verbose=INFO
+ +

The --application=PswGen command line option specifies the name of the application and also means that the xml file + should be called PswGen.xml. Different applications can be run in the same build directories by changing the name + of the application. The default application is called eVaf and if no name is given, then the xml file should be called + eVaf.xml.

+ +

The --dataroot=${HOME}/evaf/build command line option specifies the location of data files for the application. The + application assumes that etc and spool directories are sub-directories in the data root directory. The default + data root directory is ${HOME}/.local/share/data on + Linux.

+ +

Finally, the --verbose=INFO command line option makes the application to be verbose and output all the info + messages to the console.

+ +

Releasing the PswGen application

+ +

Now the application is written and tested. We are ready to make a release build and ship it.

+ +

Clean the build directory:

+ +
evaf/build $ make clean && rm CMakeCache.txt
+ +

Prepare for a release build and build the application:

+ +
evaf/build $ cmake -DCMAKE_BUILD_TYPE=Release ..
+evaf/build $ make eVafGUI SdiWindow PswGen PswStorage PswGui
+ +

Packaging and shipping the application is out of the scope of this tutorial and involves more than just copying files that + we built. As a minimum, we have to make sure that the target system has Qt libraries installed. In this tutorial, we simply copy the + released application into the {$HOME}/bin/evaf directory. Qt libraries are already installed and we do not need to + worry about them.

+ +

Create the ${HOME}/bin/evaf directory:

+ +
evaf/build $ mkdir -p ${HOME}/bin/evaf
+ +

Copy the content of bin and etc directories

+ +
evaf/build $ cp -R bin etc ${HOME}/bin/evaf/
+ +

Create the bash script PswGen in the ${HOME}/bin directory:

+ +
#!/bin/bash
+EVAF_DIR=${HOME}/bin/evaf
+${EVAF_DIR}/bin/eVafGUI --rootdir=${EVAF_DIR} --dataroot=${EVAF_DIR} --application=PswGen
+
+ +

Don't forget to make it runnable:

+ +
 bin $ chmod a+x PswGen
+ +

Now we can run the application by simply running the bash script PswGen in the ${HOME}/bin directory. + + +