Next: 11 - Building GUI Module, Previous: 09 - 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. Gets the main window interface iSdiWindow.
  3. Adds our widgets to the main window layout.
  4. Connects signals on widgets to slots in this class.
  5. Uses actions QAction to add keyboard shortcuts -- Enter key generates a password and Esc key quits the application.
  6. Sets the mReady flag to true when all this is done.

The main window has an empty QVBoxLayout layout and we could add our widgets directly to this layout, but for keyboard shortcuts to work properly, we need a widget that fills the main window. We create a master widget, add it to the main window layout and then fill the master widget with other widgets.

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");

    QWidget * masterWidget = new QWidget;
    win->addWidget(masterWidget);

    QVBoxLayout * v = new QVBoxLayout;
    masterWidget->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);
    masterWidget->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(masterWidget);
    a->setShortcut(Qt::Key_Return);
    connect(a, SIGNAL(triggered()), this, SLOT(generateClicked()));
    masterWidget->addAction(a);

    a = new QAction(masterWidget);
    a->setShortcut(Qt::Key_Escape);
    connect(a, SIGNAL(triggered()), qApp, SLOT(quit()));
    masterWidget->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");

    QWidget * masterWidget = new QWidget;
    win->addWidget(masterWidget);

    QVBoxLayout * v = new QVBoxLayout;
    masterWidget->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);
    masterWidget->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(masterWidget);
    a->setShortcut(Qt::Key_Return);
    connect(a, SIGNAL(triggered()), this, SLOT(generateClicked()));
    masterWidget->addAction(a);

    a = new QAction(masterWidget);
    a->setShortcut(Qt::Key_Escape);
    connect(a, SIGNAL(triggered()), qApp, SLOT(quit()));
    masterWidget->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.