tInitial implementation for adding new passwords - sailfish-safe - Sailfish frontend for safe(1)
 (HTM) git clone git://git.z3bra.org/sailfish-safe.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit abbdabdccfbf416dd57bee203b7d0ca8fc72bbf6
 (DIR) parent 2b1e7c91cc4f6ec37d1f8a66f45d15680d7837ea
 (HTM) Author: Daniel Vrátil <dvratil@kde.org>
       Date:   Mon, 18 Feb 2019 23:33:52 +0100
       
       Initial implementation for adding new passwords
       
       Needs a bit of clean up, adding a UI to remove and possibly even
       edit a password should come next.
       
       Diffstat:
         M harbour-passilic.pro                |      10 +++++++---
         M qml/components/GlobalPullDownMenu.… |       9 +++++++++
         A qml/pages/GeneratePasswordDialog.q… |      80 +++++++++++++++++++++++++++++++
         A qml/pages/NewPasswordDialog.qml     |     119 +++++++++++++++++++++++++++++++
         M qml/pages/PasswordListPage.qml      |      10 +++++++++-
         M src/main.cpp                        |       5 +++++
         A src/passwordgenerator.cpp           |      46 +++++++++++++++++++++++++++++++
         A src/passwordgenerator.h             |      34 +++++++++++++++++++++++++++++++
         M src/passwordprovider.cpp            |      25 +++++++++++++++----------
         M src/passwordprovider.h              |       9 +++++++++
         M src/passwordsmodel.cpp              |      49 +++++++++++++++++++++++++++++++
         M src/passwordsmodel.h                |       2 ++
         M src/passwordsortproxymodel.cpp      |       6 ++++++
         M src/passwordsortproxymodel.h        |       2 ++
         M translations/harbour-passilic-cs.ts |      34 +++++++++++++++++++++++++++++++
         M translations/harbour-passilic-fr.ts |      34 +++++++++++++++++++++++++++++++
         M translations/harbour-passilic-zh.ts |      34 +++++++++++++++++++++++++++++++
         M translations/harbour-passilic.ts    |      34 +++++++++++++++++++++++++++++++
       
       18 files changed, 528 insertions(+), 14 deletions(-)
       ---
 (DIR) diff --git a/harbour-passilic.pro b/harbour-passilic.pro
       t@@ -23,7 +23,8 @@ SOURCES += \
            src/passwordsmodel.cpp \
            src/passwordsortproxymodel.cpp \
            3rdparty/kitemmodels/kdescendantsproxymodel.cpp \
       -    src/settings.cpp
       +    src/settings.cpp \
       +    src/passwordgenerator.cpp
        
        
        HEADERS += \
       t@@ -35,7 +36,8 @@ HEADERS += \
            src/passwordsortproxymodel.h \
            3rdparty/kitemmodels/kdescendantsproxymodel.h \
            src/settings.h \
       -    src/scopeguard.h
       +    src/scopeguard.h \
       +    src/passwordgenerator.h
        
        DISTFILES += \
            qml/harbour-passilic.qml \
       t@@ -52,7 +54,9 @@ DISTFILES += \
            harbour-passilic.desktop \
            qml/pages/SearchPage.qml \
            qml/components/PasswordDelegate.qml \
       -    qml/pages/SettingsPage.qml
       +    qml/pages/SettingsPage.qml \
       +    qml/pages/NewPasswordDialog.qml \
       +    qml/pages/GeneratePasswordDialog.qml
        
        OTHER_FILES += \
            README.md
 (DIR) diff --git a/qml/components/GlobalPullDownMenu.qml b/qml/components/GlobalPullDownMenu.qml
       t@@ -20,6 +20,9 @@ import QtQml.Models 2.2
        import Sailfish.Silica 1.0
        
        PullDownMenu {
       +    property var currentIndex
       +    property var model
       +
            MenuItem {
                text: qsTr("About")
                onClicked: app.pageStack.push(Qt.resolvedUrl("../pages/AboutPage.qml"))
       t@@ -29,6 +32,12 @@ PullDownMenu {
                onClicked: app.pageStack.push(Qt.resolvedUrl("../pages/SettingsPage.qml"))
            }
            MenuItem {
       +        text: qsTr("New Password")
       +        onClicked: app.pageStack.push(Qt.resolvedUrl("../pages/NewPasswordDialog.qml"),
       +                                      { "currentIndex": currentIndex,
       +                                        "model": model })
       +    }
       +    MenuItem {
                text: qsTr("Search")
                onClicked: app.pageStack.push(searchPage)
            }
 (DIR) diff --git a/qml/pages/GeneratePasswordDialog.qml b/qml/pages/GeneratePasswordDialog.qml
       t@@ -0,0 +1,80 @@
       +/*
       + *   Copyright (C) 2019  Daniel Vrátil <dvratil@kde.org>
       + *
       + *   This program is free software: you can redistribute it and/or modify
       + *   it under the terms of the GNU General Public License as published by
       + *   the Free Software Foundation, either version 3 of the License, or
       + *   (at your option) any later version.
       + *
       + *   This program is distributed in the hope that it will be useful,
       + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
       + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       + *   GNU General Public License for more details.
       + *
       + *   You should have received a copy of the GNU General Public License
       + *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
       + */
       +
       +import QtQuick 2.2
       +import QtQml.Models 2.2
       +import Sailfish.Silica 1.0
       +
       +Dialog {
       +
       +    property alias passLen : passLenSlider.value
       +    property alias allowSymbols: symbolsSwitch.checked
       +
       +    DialogHeader {
       +        id: header
       +        width: parent.width
       +    }
       +
       +    SilicaFlickable {
       +        anchors {
       +            top: header.bottom
       +            left: parent.left
       +            right: parent.right
       +            bottom: parent.bottom
       +        }
       +
       +        clip: true
       +        contentHeight: column.height
       +
       +        Column {
       +            id: column
       +
       +            anchors {
       +                left: parent.left
       +                right: parent.right
       +                leftMargin: Theme.horizontalPageMargin
       +                rightMargin: Theme.horizontalPageMargin
       +            }
       +
       +            spacing: Theme.paddingMedium
       +
       +            Label {
       +                text: qsTr("Password Length")
       +                width: parent.width
       +            }
       +
       +            Slider {
       +                id: passLenSlider
       +                width: parent.width
       +
       +                minimumValue: 6
       +                maximumValue: 64
       +                stepSize: 1
       +                value: 20
       +                valueText: value
       +            }
       +
       +            TextSwitch {
       +                id: symbolsSwitch
       +                width: parent.width
       +
       +                text: qsTr("Allow non-alphanumeric characters")
       +                checked: true
       +            }
       +        }
       +    }
       +}
 (DIR) diff --git a/qml/pages/NewPasswordDialog.qml b/qml/pages/NewPasswordDialog.qml
       t@@ -0,0 +1,119 @@
       +/*
       + *   Copyright (C) 2019  Daniel Vrátil <dvratil@kde.org>
       + *
       + *   This program is free software: you can redistribute it and/or modify
       + *   it under the terms of the GNU General Public License as published by
       + *   the Free Software Foundation, either version 3 of the License, or
       + *   (at your option) any later version.
       + *
       + *   This program is distributed in the hope that it will be useful,
       + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
       + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       + *   GNU General Public License for more details.
       + *
       + *   You should have received a copy of the GNU General Public License
       + *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
       + */
       +
       +import QtQuick 2.2
       +import QtQml.Models 2.2
       +import Sailfish.Silica 1.0
       +import harbour.passilic 1.0
       +
       +Dialog {
       +    id: newPasswordDialog
       +
       +    property var model
       +    property var currentIndex
       +
       +    canAccept: nameField.text !== "" && passwordField.text !== ""
       +
       +    onAccepted: {
       +        model.addPassword(currentIndex, nameField.text, passwordField.text, extrasField.text)
       +    }
       +
       +    DialogHeader {
       +        id: header
       +        width: parent.width
       +    }
       +
       +    SilicaFlickable {
       +        id: flickable
       +
       +        anchors {
       +            top: header.bottom
       +            left: parent.left
       +            right: parent.right
       +            bottom: parent.bottom
       +        }
       +
       +        clip: true
       +        contentHeight: column.height
       +
       +        Column {
       +            id: column
       +
       +            anchors {
       +                left: parent.left
       +                right: parent.right
       +                leftMargin: Theme.horizontalPageMargin
       +                rightMargin: Theme.horizontalPageMargin
       +            }
       +
       +            spacing: Theme.paddingMedium
       +
       +            Label {
       +                text: qsTr("Name:")
       +                width: parent.width
       +            }
       +
       +            TextField {
       +                id: nameField
       +                width: parent.width
       +            }
       +
       +            Label {
       +                text: qsTr("Password:")
       +                width: parent.width
       +            }
       +
       +            PasswordField {
       +                id: passwordField
       +                width: parent.width
       +            }
       +
       +            Button {
       +                id: generatePassButton
       +                width: parent.width
       +                text: qsTr("Generate Password")
       +
       +                onClicked: app.pageStack.push(genPassDialog)
       +            }
       +
       +            Label {
       +                text: qsTr("Additional Info:")
       +                width: parent.width
       +            }
       +
       +            TextArea {
       +                id: extrasField
       +                width: parent.width
       +            }
       +        }
       +
       +
       +        HorizontalScrollDecorator {
       +            flickable: parent
       +        }
       +    }
       +
       +    Component {
       +        id: genPassDialog
       +
       +        GeneratePasswordDialog {
       +            onAccepted: {
       +                passwordField.text = PasswordGenerator.generate(passLen, allowSymbols)
       +            }
       +        }
       +    }
       +}
 (DIR) diff --git a/qml/pages/PasswordListPage.qml b/qml/pages/PasswordListPage.qml
       t@@ -33,6 +33,11 @@ Page {
            signal passwordRequested(var requester)
        
        
       +    Connections {
       +        target: model
       +        onModelReset: app.pageStack.pop(passwordListPage, PageStackAction.Immediate)
       +    }
       +
            SilicaListView {
                id: listView
        
       t@@ -44,7 +49,10 @@ Page {
                    title: passwordListPage.currentPath === "" ? qsTr("Passilic") : passwordListPage.currentPath
                }
        
       -        GlobalPullDownMenu {}
       +        GlobalPullDownMenu {
       +            currentIndex: passwordListPage.rootIndex
       +            model: passwordListPage.model
       +        }
        
                model: DelegateModel {
                    id: delegateModel
 (DIR) diff --git a/src/main.cpp b/src/main.cpp
       t@@ -18,6 +18,7 @@
        #include "passwordsmodel.h"
        #include "passwordfiltermodel.h"
        #include "passwordsortproxymodel.h"
       +#include "passwordgenerator.h"
        #include "imageprovider.h"
        #include "scopeguard.h"
        #include "settings.h"
       t@@ -54,6 +55,10 @@ int main(int argc, char *argv[])
                                               [](QQmlEngine *, QJSEngine *) -> QObject* {
                                                    return Settings::self();
                                               });
       +    qmlRegisterSingletonType<PasswordGenerator>("harbour.passilic", 1, 0, "PasswordGenerator",
       +                                                [](QQmlEngine *, QJSEngine *) -> QObject* {
       +                                                    return new PasswordGenerator;
       +                                                });
        
            addImageProvider(view->engine(), QStringLiteral("passIcon"));
            addImageProvider(view->engine(), QStringLiteral("passImage"));
 (DIR) diff --git a/src/passwordgenerator.cpp b/src/passwordgenerator.cpp
       t@@ -0,0 +1,46 @@
       +/*
       + *   Copyright (C) 2019  Daniel Vrátil <dvratil@kde.org>
       + *
       + *   This program is free software: you can redistribute it and/or modify
       + *   it under the terms of the GNU General Public License as published by
       + *   the Free Software Foundation, either version 3 of the License, or
       + *   (at your option) any later version.
       + *
       + *   This program is distributed in the hope that it will be useful,
       + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
       + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       + *   GNU General Public License for more details.
       + *
       + *   You should have received a copy of the GNU General Public License
       + *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
       + */
       +
       +#include "passwordgenerator.h"
       +
       +#include <QFile>
       +
       +#include <ctype.h>
       +
       +PasswordGenerator::PasswordGenerator()
       +{
       +}
       +
       +QString PasswordGenerator::generate(int len, bool allowSymbols)
       +{
       +    QString pass;
       +    pass.reserve(len);
       +
       +    QFile urand(QStringLiteral("/dev/urandom"));
       +    if (!urand.open(QIODevice::ReadOnly)) {
       +        return {};
       +    }
       +
       +    while (pass.size() < len) {
       +        const char c = urand.read(1)[0];
       +        if (isalnum(c) || (allowSymbols && isprint(c))) {
       +            pass.append(QLatin1Char(c));
       +        }
       +    }
       +
       +    return pass;
       +}
 (DIR) diff --git a/src/passwordgenerator.h b/src/passwordgenerator.h
       t@@ -0,0 +1,34 @@
       +/*
       + *   Copyright (C) 2019  Daniel Vrátil <dvratil@kde.org>
       + *
       + *   This program is free software: you can redistribute it and/or modify
       + *   it under the terms of the GNU General Public License as published by
       + *   the Free Software Foundation, either version 3 of the License, or
       + *   (at your option) any later version.
       + *
       + *   This program is distributed in the hope that it will be useful,
       + *   but WITHOUT ANY WARRANTY; without even the implied warranty of
       + *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       + *   GNU General Public License for more details.
       + *
       + *   You should have received a copy of the GNU General Public License
       + *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
       + */
       +
       +#ifndef PASSWORDGENERATOR_H
       +#define PASSWORDGENERATOR_H
       +
       +#include <QObject>
       +
       +class PasswordGenerator : public QObject
       +{
       +    Q_OBJECT
       +
       +public:
       +    PasswordGenerator();
       +
       +public Q_SLOTS:
       +    QString generate(int len, bool allowSymbols);
       +};
       +
       +#endif // PASSWORDGENERATOR_H
 (DIR) diff --git a/src/passwordprovider.cpp b/src/passwordprovider.cpp
       t@@ -85,6 +85,16 @@ void PasswordProvider::expirePassword()
            deleteLater();
        }
        
       +PasswordProvider::GpgExecutable PasswordProvider::findGpgExecutable()
       +{
       +    auto gpgExe = QStandardPaths::findExecutable(QStringLiteral("gpg2"));
       +    if (gpgExe.isEmpty()) {
       +        gpgExe = QStandardPaths::findExecutable(QStringLiteral("gpg"));
       +        return {gpgExe, false};
       +    }
       +    return {gpgExe, true};
       +}
       +
        void PasswordProvider::requestPassword()
        {
            setError({});
       t@@ -103,13 +113,8 @@ void PasswordProvider::requestPassword()
                        }
                    });
        
       -    bool isGpg2 = true;
       -    auto gpgExe = QStandardPaths::findExecutable(QStringLiteral("gpg2"));
       -    if (gpgExe.isEmpty()) {
       -        gpgExe = QStandardPaths::findExecutable(QStringLiteral("gpg"));
       -        isGpg2 = false;
       -    }
       -    if (gpgExe.isEmpty()) {
       +    const auto gpgExe = findGpgExecutable();
       +    if (gpgExe.path.isEmpty()) {
                qWarning("Failed to find gpg or gpg2 executables");
                setError(tr("Failed to decrypt password: GPG is not available"));
                return;
       t@@ -122,7 +127,7 @@ void PasswordProvider::requestPassword()
                                 QStringLiteral("--no-encrypt-to"),
                                 QStringLiteral("--passphrase-fd=0"),
                                 mPath };
       -    if (isGpg2) {
       +    if (gpgExe.isGpg2) {
                args = QStringList{ QStringLiteral("--pinentry-mode=loopback"),
                                    QStringLiteral("--batch"),
                                    QStringLiteral("--use-agent") }
       t@@ -133,7 +138,7 @@ void PasswordProvider::requestPassword()
            connect(mGpg, &QProcess::errorOccurred,
                    this, [this, gpgExe](QProcess::ProcessError state) {
                        if (state == QProcess::FailedToStart) {
       -                    qWarning("Failed to start %s: %s", qUtf8Printable(gpgExe), qUtf8Printable(mGpg->errorString()));
       +                    qWarning("Failed to start %s: %s", qUtf8Printable(gpgExe.path), qUtf8Printable(mGpg->errorString()));
                            setError(tr("Failed to decrypt password: Failed to start GPG"));
                        }
                    });
       t@@ -156,7 +161,7 @@ void PasswordProvider::requestPassword()
                        mGpg->deleteLater();
                        mGpg = nullptr;
                    });
       -    mGpg->setProgram(gpgExe);
       +    mGpg->setProgram(gpgExe.path);
            mGpg->setArguments(args);
            mGpg->start(QIODevice::ReadWrite);
        }
 (DIR) diff --git a/src/passwordprovider.h b/src/passwordprovider.h
       t@@ -47,6 +47,15 @@ public:
            bool hasError() const;
            QString error() const;
        
       +    struct GpgExecutable {
       +        GpgExecutable(const QString &path, bool isGpg2)
       +            : path(path), isGpg2(isGpg2)
       +        {}
       +        QString path = {};
       +        bool isGpg2 = false;
       +    };
       +
       +    static GpgExecutable findGpgExecutable();
        public Q_SLOTS:
            void requestPassword();
            void cancel();
 (DIR) diff --git a/src/passwordsmodel.cpp b/src/passwordsmodel.cpp
       t@@ -23,6 +23,9 @@
        #include <QDir>
        #include <QDebug>
        #include <QPointer>
       +#include <QProcess>
       +#include <QTemporaryFile>
       +#include <QFile>
        
        #define PASSWORD_STORE_DIR "PASSWORD_STORE_DIR"
        
       t@@ -219,3 +222,49 @@ void PasswordsModel::populateDir(const QDir& dir, Node *parent)
                populateDir(entry.absoluteFilePath(), node);
            }
        }
       +
       +// FIXME: This is absolutely not the right place for this piece of code
       +// Should introduce PasswordManager to abstract password generation,
       +// creation and access in an asynchronous manner.
       +void PasswordsModel::addPassword(const QModelIndex &parent, const QString &name,
       +                                 const QString &password, const QString &extras)
       +{
       +    auto node = this->node(parent);
       +
       +    // Escape forward slash to avoid the name "escaping" the current folder
       +    QString safeName = name;
       +    safeName.replace(QLatin1Char('/'), QLatin1Char(' '));
       +
       +    QFile gpgIdFile(mRoot->path() + QStringLiteral("/.gpg-id"));
       +    if (!gpgIdFile.exists()) {
       +        qWarning() << "Missing .gpg-id file (" << gpgIdFile.fileName() << ")";
       +        return;
       +    }
       +    gpgIdFile.open(QIODevice::ReadOnly);
       +    const auto gpgId = QString::fromUtf8(gpgIdFile.readAll()).trimmed();
       +    gpgIdFile.close();
       +
       +
       +    const auto gpgExe = PasswordProvider::findGpgExecutable();
       +    if (gpgExe.path.isEmpty()) {
       +        qWarning() << "Failed to find GPG executable";
       +        return;
       +    }
       +
       +    QProcess process;
       +    process.setProgram(gpgExe.path);
       +    process.setArguments({ QStringLiteral("-e"),
       +                           QStringLiteral("--no-tty"),
       +                           QStringLiteral("-r%1").arg(gpgId),
       +                           QStringLiteral("-o%1/%2.gpg").arg(node->path(), safeName)
       +                         });
       +    process.start(QIODevice::ReadWrite);
       +    process.waitForStarted();
       +    process.write(password.toUtf8());
       +    if (!extras.isEmpty()) {
       +        process.write("\n");
       +        process.write(extras.toUtf8());
       +    }
       +    process.closeWriteChannel();
       +    process.waitForFinished();
       +}
 (DIR) diff --git a/src/passwordsmodel.h b/src/passwordsmodel.h
       t@@ -58,6 +58,8 @@ public:
        
            QVariant data(const QModelIndex &index, int role) const override;
        
       +    Q_INVOKABLE void addPassword(const QModelIndex &parent, const QString &name,
       +                                 const QString &password, const QString &extras);
        private:
            void populate();
            void populateDir(const QDir &dir, Node *parent);
 (DIR) diff --git a/src/passwordsortproxymodel.cpp b/src/passwordsortproxymodel.cpp
       t@@ -40,3 +40,9 @@ bool PasswordSortProxyModel::lessThan(const QModelIndex &source_left, const QMod
        
            return QSortFilterProxyModel::lessThan(source_left, source_right);
        }
       +
       +void PasswordSortProxyModel::addPassword(const QModelIndex &parent, const QString &name,
       +                                         const QString &password, const QString &extras)
       +{
       +    qobject_cast<PasswordsModel*>(sourceModel())->addPassword(mapToSource(parent), name, password, extras);
       +}
 (DIR) diff --git a/src/passwordsortproxymodel.h b/src/passwordsortproxymodel.h
       t@@ -28,6 +28,8 @@ class PasswordSortProxyModel : public QSortFilterProxyModel
        public:
            explicit PasswordSortProxyModel(QObject *parent = nullptr);
        
       +    Q_INVOKABLE void addPassword(const QModelIndex &parent, const QString &name,
       +                                 const QString &password, const QString &extra);
        protected:
            bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
        };
 (DIR) diff --git a/translations/harbour-passilic-cs.ts b/translations/harbour-passilic-cs.ts
       t@@ -29,6 +29,17 @@
            </message>
        </context>
        <context>
       +    <name>GeneratePasswordDialog</name>
       +    <message>
       +        <source>Password Length</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +    <message>
       +        <source>Allow non-alphanumeric characters</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +</context>
       +<context>
            <name>GlobalPullDownMenu</name>
            <message>
                <source>About</source>
       t@@ -42,6 +53,29 @@
                <source>Settings</source>
                <translation>Nastavení</translation>
            </message>
       +    <message>
       +        <source>New Password</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +</context>
       +<context>
       +    <name>NewPasswordDialog</name>
       +    <message>
       +        <source>Name:</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +    <message>
       +        <source>Password:</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +    <message>
       +        <source>Additional Info:</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +    <message>
       +        <source>Generate Password</source>
       +        <translation type="unfinished"></translation>
       +    </message>
        </context>
        <context>
            <name>PasswordDelegate</name>
 (DIR) diff --git a/translations/harbour-passilic-fr.ts b/translations/harbour-passilic-fr.ts
       t@@ -29,6 +29,17 @@
            </message>
        </context>
        <context>
       +    <name>GeneratePasswordDialog</name>
       +    <message>
       +        <source>Password Length</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +    <message>
       +        <source>Allow non-alphanumeric characters</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +</context>
       +<context>
            <name>GlobalPullDownMenu</name>
            <message>
                <source>About</source>
       t@@ -42,6 +53,29 @@
                <source>Settings</source>
                <translation>Paramètres</translation>
            </message>
       +    <message>
       +        <source>New Password</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +</context>
       +<context>
       +    <name>NewPasswordDialog</name>
       +    <message>
       +        <source>Name:</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +    <message>
       +        <source>Password:</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +    <message>
       +        <source>Additional Info:</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +    <message>
       +        <source>Generate Password</source>
       +        <translation type="unfinished"></translation>
       +    </message>
        </context>
        <context>
            <name>PasswordDelegate</name>
 (DIR) diff --git a/translations/harbour-passilic-zh.ts b/translations/harbour-passilic-zh.ts
       t@@ -29,6 +29,17 @@
            </message>
        </context>
        <context>
       +    <name>GeneratePasswordDialog</name>
       +    <message>
       +        <source>Password Length</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +    <message>
       +        <source>Allow non-alphanumeric characters</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +</context>
       +<context>
            <name>GlobalPullDownMenu</name>
            <message>
                <source>About</source>
       t@@ -42,6 +53,29 @@
                <source>Settings</source>
                <translation>设置</translation>
            </message>
       +    <message>
       +        <source>New Password</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +</context>
       +<context>
       +    <name>NewPasswordDialog</name>
       +    <message>
       +        <source>Name:</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +    <message>
       +        <source>Password:</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +    <message>
       +        <source>Additional Info:</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +    <message>
       +        <source>Generate Password</source>
       +        <translation type="unfinished"></translation>
       +    </message>
        </context>
        <context>
            <name>PasswordDelegate</name>
 (DIR) diff --git a/translations/harbour-passilic.ts b/translations/harbour-passilic.ts
       t@@ -29,6 +29,17 @@
            </message>
        </context>
        <context>
       +    <name>GeneratePasswordDialog</name>
       +    <message>
       +        <source>Password Length</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +    <message>
       +        <source>Allow non-alphanumeric characters</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +</context>
       +<context>
            <name>GlobalPullDownMenu</name>
            <message>
                <source>About</source>
       t@@ -42,6 +53,29 @@
                <source>Settings</source>
                <translation type="unfinished"></translation>
            </message>
       +    <message>
       +        <source>New Password</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +</context>
       +<context>
       +    <name>NewPasswordDialog</name>
       +    <message>
       +        <source>Name:</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +    <message>
       +        <source>Password:</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +    <message>
       +        <source>Additional Info:</source>
       +        <translation type="unfinished"></translation>
       +    </message>
       +    <message>
       +        <source>Generate Password</source>
       +        <translation type="unfinished"></translation>
       +    </message>
        </context>
        <context>
            <name>PasswordDelegate</name>