<commit_before>/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "cppfilesettingspage.h"

#include "cpptoolsconstants.h"
#include "cpptoolsplugin.h"
#include <ui_cppfilesettingspage.h>

#include <coreplugin/icore.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/mimedatabase.h>
#include <cppeditor/cppeditorconstants.h>

#include <utils/environment.h>

#include <QSettings>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QCoreApplication>
#include <QDate>
#include <QLocale>
#include <QTextCodec>
#include <QTextStream>
#include <QFileDialog>

static const char headerSuffixKeyC[] = "HeaderSuffix";
static const char sourceSuffixKeyC[] = "SourceSuffix";
static const char headerSearchPathsKeyC[] = "HeaderSearchPaths";
static const char sourceSearchPathsKeyC[] = "SourceSearchPaths";
static const char licenseTemplatePathKeyC[] = "LicenseTemplate";

const char *licenseTemplateTemplate = QT_TRANSLATE_NOOP("CppTools::Internal::CppFileSettingsWidget",
"/**************************************************************************\n"
"** Qt Creator license header template\n"
"**   Special keywords: %USER% %DATE% %YEAR%\n"
"**   Environment variables: %$VARIABLE%\n"
"**   To protect a percent sign, use '%%'.\n"
"**************************************************************************/\n");

namespace CppTools {
namespace Internal {

CppFileSettings::CppFileSettings() :
    lowerCaseFiles(false)
{
}

void CppFileSettings::toSettings(QSettings *s) const
{
    s->beginGroup(QLatin1String(Constants::CPPTOOLS_SETTINGSGROUP));
    s->setValue(QLatin1String(headerSuffixKeyC), headerSuffix);
    s->setValue(QLatin1String(sourceSuffixKeyC), sourceSuffix);
    s->setValue(QLatin1String(headerSearchPathsKeyC), headerSearchPaths);
    s->setValue(QLatin1String(sourceSearchPathsKeyC), sourceSearchPaths);
    s->setValue(QLatin1String(Constants::LOWERCASE_CPPFILES_KEY), lowerCaseFiles);
    s->setValue(QLatin1String(licenseTemplatePathKeyC), licenseTemplatePath);
    s->endGroup();
}

void CppFileSettings::fromSettings(QSettings *s)
{
    const QStringList defaultHeaderSearchPaths = QStringList()
            << QLatin1String("include")
            << QLatin1String("Include")
            << QDir::toNativeSeparators(QLatin1String("../include"))
            << QDir::toNativeSeparators(QLatin1String("../Include"));
    const QStringList defaultSourceSearchPaths = QStringList()
            << QDir::toNativeSeparators(QLatin1String("../src"))
            << QDir::toNativeSeparators(QLatin1String("../Src"))
            << QLatin1String("..");
    s->beginGroup(QLatin1String(Constants::CPPTOOLS_SETTINGSGROUP));
    headerSuffix= s->value(QLatin1String(headerSuffixKeyC), QLatin1String("h")).toString();
    sourceSuffix = s->value(QLatin1String(sourceSuffixKeyC), QLatin1String("cpp")).toString();
    headerSearchPaths = s->value(QLatin1String(headerSearchPathsKeyC), defaultHeaderSearchPaths)
            .toStringList();
    sourceSearchPaths = s->value(QLatin1String(sourceSearchPathsKeyC), defaultSourceSearchPaths)
            .toStringList();
    const bool lowerCaseDefault = Constants::lowerCaseFilesDefault;
    lowerCaseFiles = s->value(QLatin1String(Constants::LOWERCASE_CPPFILES_KEY), QVariant(lowerCaseDefault)).toBool();
    licenseTemplatePath = s->value(QLatin1String(licenseTemplatePathKeyC), QString()).toString();
    s->endGroup();
}

bool CppFileSettings::applySuffixesToMimeDB()
{
    return Core::MimeDatabase::setPreferredSuffix(QLatin1String(CppTools::Constants::CPP_SOURCE_MIMETYPE), sourceSuffix)
            && Core::MimeDatabase::setPreferredSuffix(QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE), headerSuffix);
}

bool CppFileSettings::equals(const CppFileSettings &rhs) const
{
    return lowerCaseFiles == rhs.lowerCaseFiles
           && headerSuffix == rhs.headerSuffix
           && sourceSuffix == rhs.sourceSuffix
           && headerSearchPaths == rhs.headerSearchPaths
           && sourceSearchPaths == rhs.sourceSearchPaths
           && licenseTemplatePath == rhs.licenseTemplatePath;
}

// Replacements of special license template keywords.
static bool keyWordReplacement(const QString &keyWord,
                               const QString &file,
                               const QString &className,
                               QString *value)
{
    if (keyWord == QLatin1String("%YEAR%")) {
        *value = QString::number(QDate::currentDate().year());
        return true;
    }
    if (keyWord == QLatin1String("%MONTH%")) {
        *value = QString::number(QDate::currentDate().month());
        return true;
    }
    if (keyWord == QLatin1String("%DAY%")) {
        *value = QString::number(QDate::currentDate().day());
        return true;
    }
    if (keyWord == QLatin1String("%CLASS%")) {
        *value = className;
        return true;
    }
    if (keyWord == QLatin1String("%FILENAME%")) {
        *value = QFileInfo(file).fileName();
        return true;
    }
    if (keyWord == QLatin1String("%DATE%")) {
        static QString format;
        // ensure a format with 4 year digits. Some have locales have 2.
        if (format.isEmpty()) {
            QLocale loc;
            format = loc.dateFormat(QLocale::ShortFormat);
            const QChar ypsilon = QLatin1Char('y');
            if (format.count(ypsilon) == 2)
                format.insert(format.indexOf(ypsilon), QString(2, ypsilon));
        }
        *value = QDate::currentDate().toString(format);
        return true;
    }
    if (keyWord == QLatin1String("%USER%")) {
        *value = Utils::Environment::systemEnvironment().userName();
        return true;
    }
    // Environment variables (for example '%$EMAIL%').
    if (keyWord.startsWith(QLatin1String("%$"))) {
        const QString varName = keyWord.mid(2, keyWord.size() - 3);
        *value = QString::fromLocal8Bit(qgetenv(varName.toLocal8Bit()));
        return true;
    }
    return false;
}

// Parse a license template, scan for %KEYWORD% and replace if known.
// Replace '%%' by '%'.
static void parseLicenseTemplatePlaceholders(QString *t, const QString &file, const QString &className)
{
    int pos = 0;
    const QChar placeHolder = QLatin1Char('%');
    do {
        const int placeHolderPos = t->indexOf(placeHolder, pos);
        if (placeHolderPos == -1)
            break;
        const int endPlaceHolderPos = t->indexOf(placeHolder, placeHolderPos + 1);
        if (endPlaceHolderPos == -1)
            break;
        if (endPlaceHolderPos == placeHolderPos + 1) { // '%%' -> '%'
            t->remove(placeHolderPos, 1);
            pos = placeHolderPos + 1;
        } else {
            const QString keyWord = t->mid(placeHolderPos, endPlaceHolderPos + 1 - placeHolderPos);
            QString replacement;
            if (keyWordReplacement(keyWord, file, className, &replacement)) {
                t->replace(placeHolderPos, keyWord.size(), replacement);
                pos = placeHolderPos + replacement.size();
            } else {
                // Leave invalid keywords as is.
                pos = endPlaceHolderPos + 1;
            }
        }
    } while (pos < t->size());
}

// Convenience that returns the formatted license template.
QString CppFileSettings::licenseTemplate(const QString &fileName, const QString &className)
{

    const QSettings *s = Core::ICore::settings();
    QString key = QLatin1String(Constants::CPPTOOLS_SETTINGSGROUP);
    key += QLatin1Char('/');
    key += QLatin1String(licenseTemplatePathKeyC);
    const QString path = s->value(key, QString()).toString();
    if (path.isEmpty())
        return QString();
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly|QIODevice::Text)) {
        qWarning("Unable to open the license template %s: %s", qPrintable(path), qPrintable(file.errorString()));
        return QString();
    }

    QTextStream licenseStream(&file);
    licenseStream.setCodec(Core::EditorManager::defaultTextCodec());
    licenseStream.setAutoDetectUnicode(true);
    QString license = licenseStream.readAll();

    parseLicenseTemplatePlaceholders(&license, fileName, className);
    // Ensure exactly one additional new line separating stuff
    const QChar newLine = QLatin1Char('\n');
    if (!license.endsWith(newLine))
        license += newLine;
    license += newLine;
    return license;
}

// ------------------ CppFileSettingsWidget

CppFileSettingsWidget::CppFileSettingsWidget(QWidget *parent) :
    QWidget(parent),
    m_ui(new Internal::Ui::CppFileSettingsPage)
{
    m_ui->setupUi(this);
    // populate suffix combos
    if (const Core::MimeType sourceMt = Core::MimeDatabase::findByType(QLatin1String(CppTools::Constants::CPP_SOURCE_MIMETYPE)))
        foreach (const QString &suffix, sourceMt.suffixes())
            m_ui->sourceSuffixComboBox->addItem(suffix);

    if (const Core::MimeType headerMt = Core::MimeDatabase::findByType(QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE)))
        foreach (const QString &suffix, headerMt.suffixes())
            m_ui->headerSuffixComboBox->addItem(suffix);
    m_ui->licenseTemplatePathChooser->setExpectedKind(Utils::PathChooser::File);
    m_ui->licenseTemplatePathChooser->addButton(tr("Edit..."), this, SLOT(slotEdit()));
}

CppFileSettingsWidget::~CppFileSettingsWidget()
{
    delete m_ui;
}

QString CppFileSettingsWidget::licenseTemplatePath() const
{
    return m_ui->licenseTemplatePathChooser->path();
}

void CppFileSettingsWidget::setLicenseTemplatePath(const QString &lp)
{
    m_ui->licenseTemplatePathChooser->setPath(lp);
}

static QStringList trimmedPaths(const QString &paths)
{
    QStringList res;
    foreach (const QString &path, paths.split(QLatin1Char(','), QString::SkipEmptyParts))
        res << path.trimmed();
    return res;
}

CppFileSettings CppFileSettingsWidget::settings() const
{
    CppFileSettings rc;
    rc.lowerCaseFiles = m_ui->lowerCaseFileNamesCheckBox->isChecked();
    rc.headerSuffix = m_ui->headerSuffixComboBox->currentText();
    rc.sourceSuffix = m_ui->sourceSuffixComboBox->currentText();
    rc.headerSearchPaths = trimmedPaths(m_ui->headerSearchPathsEdit->text());
    rc.sourceSearchPaths = trimmedPaths(m_ui->sourceSearchPathsEdit->text());
    rc.licenseTemplatePath = licenseTemplatePath();
    return rc;
}

QString CppFileSettingsWidget::searchKeywords() const
{
    QString rc;
    QTextStream(&rc) << m_ui->headersGroupBox->title()
            << ' ' << m_ui->headerSuffixLabel->text()
            << ' ' << m_ui->headerSearchPathsLabel->text()
            << ' ' << m_ui->sourcesGroupBox->title()
            << ' ' << m_ui->sourceSuffixLabel->text()
            << ' ' << m_ui->sourceSearchPathsLabel->text()
            << ' ' << m_ui->lowerCaseFileNamesCheckBox->text()
            << ' ' << m_ui->licenseTemplateLabel->text();
    rc.remove(QLatin1Char('&'));
    return rc;
}

static inline void setComboText(QComboBox *cb, const QString &text, int defaultIndex = 0)
{
    const int index = cb->findText(text);
    cb->setCurrentIndex(index == -1 ? defaultIndex: index);
}

void CppFileSettingsWidget::setSettings(const CppFileSettings &s)
{
    m_ui->lowerCaseFileNamesCheckBox->setChecked(s.lowerCaseFiles);
    setComboText(m_ui->headerSuffixComboBox, s.headerSuffix);
    setComboText(m_ui->sourceSuffixComboBox, s.sourceSuffix);
    m_ui->headerSearchPathsEdit->setText(s.headerSearchPaths.join(QLatin1String(",")));
    m_ui->sourceSearchPathsEdit->setText(s.sourceSearchPaths.join(QLatin1String(",")));
    setLicenseTemplatePath(s.licenseTemplatePath);
}

void CppFileSettingsWidget::slotEdit()
{
    QString path = licenseTemplatePath();
    if (path.isEmpty()) {
        // Pick a file name and write new template, edit with C++
        path = QFileDialog::getSaveFileName(this, tr("Choose Location for New License Template File"));
        if (path.isEmpty())
            return;
        Utils::FileSaver saver(path, QIODevice::Text);
        saver.write(tr(licenseTemplateTemplate).toUtf8());
        if (!saver.finalize(this))
            return;
        setLicenseTemplatePath(path);
    }
    // Edit (now) existing file with C++
    Core::EditorManager::openEditor(path, CppEditor::Constants::CPPEDITOR_ID);
}

// --------------- CppFileSettingsPage
CppFileSettingsPage::CppFileSettingsPage(QSharedPointer<CppFileSettings> &settings,
                                         QObject *parent) :
    Core::IOptionsPage(parent),
    m_settings(settings)
{
    setId(Constants::CPP_FILE_SETTINGS_ID);
    setDisplayName(QCoreApplication::translate("CppTools", Constants::CPP_FILE_SETTINGS_NAME));
    setCategory(Constants::CPP_SETTINGS_CATEGORY);
    setDisplayCategory(QCoreApplication::translate("CppTools", Constants::CPP_SETTINGS_TR_CATEGORY));
    setCategoryIcon(QLatin1String(Constants::SETTINGS_CATEGORY_CPP_ICON));
}

QWidget *CppFileSettingsPage::createPage(QWidget *parent)
{

    m_widget = new CppFileSettingsWidget(parent);
    m_widget->setSettings(*m_settings);
    if (m_searchKeywords.isEmpty())
        m_searchKeywords = m_widget->searchKeywords();
    return m_widget;
}

void CppFileSettingsPage::apply()
{
    if (m_widget) {
        const CppFileSettings newSettings = m_widget->settings();
        if (newSettings != *m_settings) {
            *m_settings = newSettings;
            m_settings->toSettings(Core::ICore::settings());
            m_settings->applySuffixesToMimeDB();
            CppToolsPlugin::clearHeaderSourceCache();
        }
    }
}

bool CppFileSettingsPage::matches(const QString &s) const
{
    return m_searchKeywords.contains(s, Qt::CaseInsensitive);
}

} // namespace Internal
} // namespace CppTools
<commit_msg>CppTools: Add history completer to path choosers<commit_after>/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "cppfilesettingspage.h"

#include "cpptoolsconstants.h"
#include "cpptoolsplugin.h"
#include <ui_cppfilesettingspage.h>

#include <coreplugin/icore.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/mimedatabase.h>
#include <cppeditor/cppeditorconstants.h>

#include <utils/environment.h>

#include <QSettings>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QCoreApplication>
#include <QDate>
#include <QLocale>
#include <QTextCodec>
#include <QTextStream>
#include <QFileDialog>

static const char headerSuffixKeyC[] = "HeaderSuffix";
static const char sourceSuffixKeyC[] = "SourceSuffix";
static const char headerSearchPathsKeyC[] = "HeaderSearchPaths";
static const char sourceSearchPathsKeyC[] = "SourceSearchPaths";
static const char licenseTemplatePathKeyC[] = "LicenseTemplate";

const char *licenseTemplateTemplate = QT_TRANSLATE_NOOP("CppTools::Internal::CppFileSettingsWidget",
"/**************************************************************************\n"
"** Qt Creator license header template\n"
"**   Special keywords: %USER% %DATE% %YEAR%\n"
"**   Environment variables: %$VARIABLE%\n"
"**   To protect a percent sign, use '%%'.\n"
"**************************************************************************/\n");

namespace CppTools {
namespace Internal {

CppFileSettings::CppFileSettings() :
    lowerCaseFiles(false)
{
}

void CppFileSettings::toSettings(QSettings *s) const
{
    s->beginGroup(QLatin1String(Constants::CPPTOOLS_SETTINGSGROUP));
    s->setValue(QLatin1String(headerSuffixKeyC), headerSuffix);
    s->setValue(QLatin1String(sourceSuffixKeyC), sourceSuffix);
    s->setValue(QLatin1String(headerSearchPathsKeyC), headerSearchPaths);
    s->setValue(QLatin1String(sourceSearchPathsKeyC), sourceSearchPaths);
    s->setValue(QLatin1String(Constants::LOWERCASE_CPPFILES_KEY), lowerCaseFiles);
    s->setValue(QLatin1String(licenseTemplatePathKeyC), licenseTemplatePath);
    s->endGroup();
}

void CppFileSettings::fromSettings(QSettings *s)
{
    const QStringList defaultHeaderSearchPaths = QStringList()
            << QLatin1String("include")
            << QLatin1String("Include")
            << QDir::toNativeSeparators(QLatin1String("../include"))
            << QDir::toNativeSeparators(QLatin1String("../Include"));
    const QStringList defaultSourceSearchPaths = QStringList()
            << QDir::toNativeSeparators(QLatin1String("../src"))
            << QDir::toNativeSeparators(QLatin1String("../Src"))
            << QLatin1String("..");
    s->beginGroup(QLatin1String(Constants::CPPTOOLS_SETTINGSGROUP));
    headerSuffix= s->value(QLatin1String(headerSuffixKeyC), QLatin1String("h")).toString();
    sourceSuffix = s->value(QLatin1String(sourceSuffixKeyC), QLatin1String("cpp")).toString();
    headerSearchPaths = s->value(QLatin1String(headerSearchPathsKeyC), defaultHeaderSearchPaths)
            .toStringList();
    sourceSearchPaths = s->value(QLatin1String(sourceSearchPathsKeyC), defaultSourceSearchPaths)
            .toStringList();
    const bool lowerCaseDefault = Constants::lowerCaseFilesDefault;
    lowerCaseFiles = s->value(QLatin1String(Constants::LOWERCASE_CPPFILES_KEY), QVariant(lowerCaseDefault)).toBool();
    licenseTemplatePath = s->value(QLatin1String(licenseTemplatePathKeyC), QString()).toString();
    s->endGroup();
}

bool CppFileSettings::applySuffixesToMimeDB()
{
    return Core::MimeDatabase::setPreferredSuffix(QLatin1String(CppTools::Constants::CPP_SOURCE_MIMETYPE), sourceSuffix)
            && Core::MimeDatabase::setPreferredSuffix(QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE), headerSuffix);
}

bool CppFileSettings::equals(const CppFileSettings &rhs) const
{
    return lowerCaseFiles == rhs.lowerCaseFiles
           && headerSuffix == rhs.headerSuffix
           && sourceSuffix == rhs.sourceSuffix
           && headerSearchPaths == rhs.headerSearchPaths
           && sourceSearchPaths == rhs.sourceSearchPaths
           && licenseTemplatePath == rhs.licenseTemplatePath;
}

// Replacements of special license template keywords.
static bool keyWordReplacement(const QString &keyWord,
                               const QString &file,
                               const QString &className,
                               QString *value)
{
    if (keyWord == QLatin1String("%YEAR%")) {
        *value = QString::number(QDate::currentDate().year());
        return true;
    }
    if (keyWord == QLatin1String("%MONTH%")) {
        *value = QString::number(QDate::currentDate().month());
        return true;
    }
    if (keyWord == QLatin1String("%DAY%")) {
        *value = QString::number(QDate::currentDate().day());
        return true;
    }
    if (keyWord == QLatin1String("%CLASS%")) {
        *value = className;
        return true;
    }
    if (keyWord == QLatin1String("%FILENAME%")) {
        *value = QFileInfo(file).fileName();
        return true;
    }
    if (keyWord == QLatin1String("%DATE%")) {
        static QString format;
        // ensure a format with 4 year digits. Some have locales have 2.
        if (format.isEmpty()) {
            QLocale loc;
            format = loc.dateFormat(QLocale::ShortFormat);
            const QChar ypsilon = QLatin1Char('y');
            if (format.count(ypsilon) == 2)
                format.insert(format.indexOf(ypsilon), QString(2, ypsilon));
        }
        *value = QDate::currentDate().toString(format);
        return true;
    }
    if (keyWord == QLatin1String("%USER%")) {
        *value = Utils::Environment::systemEnvironment().userName();
        return true;
    }
    // Environment variables (for example '%$EMAIL%').
    if (keyWord.startsWith(QLatin1String("%$"))) {
        const QString varName = keyWord.mid(2, keyWord.size() - 3);
        *value = QString::fromLocal8Bit(qgetenv(varName.toLocal8Bit()));
        return true;
    }
    return false;
}

// Parse a license template, scan for %KEYWORD% and replace if known.
// Replace '%%' by '%'.
static void parseLicenseTemplatePlaceholders(QString *t, const QString &file, const QString &className)
{
    int pos = 0;
    const QChar placeHolder = QLatin1Char('%');
    do {
        const int placeHolderPos = t->indexOf(placeHolder, pos);
        if (placeHolderPos == -1)
            break;
        const int endPlaceHolderPos = t->indexOf(placeHolder, placeHolderPos + 1);
        if (endPlaceHolderPos == -1)
            break;
        if (endPlaceHolderPos == placeHolderPos + 1) { // '%%' -> '%'
            t->remove(placeHolderPos, 1);
            pos = placeHolderPos + 1;
        } else {
            const QString keyWord = t->mid(placeHolderPos, endPlaceHolderPos + 1 - placeHolderPos);
            QString replacement;
            if (keyWordReplacement(keyWord, file, className, &replacement)) {
                t->replace(placeHolderPos, keyWord.size(), replacement);
                pos = placeHolderPos + replacement.size();
            } else {
                // Leave invalid keywords as is.
                pos = endPlaceHolderPos + 1;
            }
        }
    } while (pos < t->size());
}

// Convenience that returns the formatted license template.
QString CppFileSettings::licenseTemplate(const QString &fileName, const QString &className)
{

    const QSettings *s = Core::ICore::settings();
    QString key = QLatin1String(Constants::CPPTOOLS_SETTINGSGROUP);
    key += QLatin1Char('/');
    key += QLatin1String(licenseTemplatePathKeyC);
    const QString path = s->value(key, QString()).toString();
    if (path.isEmpty())
        return QString();
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly|QIODevice::Text)) {
        qWarning("Unable to open the license template %s: %s", qPrintable(path), qPrintable(file.errorString()));
        return QString();
    }

    QTextStream licenseStream(&file);
    licenseStream.setCodec(Core::EditorManager::defaultTextCodec());
    licenseStream.setAutoDetectUnicode(true);
    QString license = licenseStream.readAll();

    parseLicenseTemplatePlaceholders(&license, fileName, className);
    // Ensure exactly one additional new line separating stuff
    const QChar newLine = QLatin1Char('\n');
    if (!license.endsWith(newLine))
        license += newLine;
    license += newLine;
    return license;
}

// ------------------ CppFileSettingsWidget

CppFileSettingsWidget::CppFileSettingsWidget(QWidget *parent) :
    QWidget(parent),
    m_ui(new Internal::Ui::CppFileSettingsPage)
{
    m_ui->setupUi(this);
    // populate suffix combos
    if (const Core::MimeType sourceMt = Core::MimeDatabase::findByType(QLatin1String(CppTools::Constants::CPP_SOURCE_MIMETYPE)))
        foreach (const QString &suffix, sourceMt.suffixes())
            m_ui->sourceSuffixComboBox->addItem(suffix);

    if (const Core::MimeType headerMt = Core::MimeDatabase::findByType(QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE)))
        foreach (const QString &suffix, headerMt.suffixes())
            m_ui->headerSuffixComboBox->addItem(suffix);
    m_ui->licenseTemplatePathChooser->setExpectedKind(Utils::PathChooser::File);
    m_ui->licenseTemplatePathChooser->setHistoryCompleter(QLatin1String("Cpp.LicenseTemplate.History"));
    m_ui->licenseTemplatePathChooser->addButton(tr("Edit..."), this, SLOT(slotEdit()));
}

CppFileSettingsWidget::~CppFileSettingsWidget()
{
    delete m_ui;
}

QString CppFileSettingsWidget::licenseTemplatePath() const
{
    return m_ui->licenseTemplatePathChooser->path();
}

void CppFileSettingsWidget::setLicenseTemplatePath(const QString &lp)
{
    m_ui->licenseTemplatePathChooser->setPath(lp);
}

static QStringList trimmedPaths(const QString &paths)
{
    QStringList res;
    foreach (const QString &path, paths.split(QLatin1Char(','), QString::SkipEmptyParts))
        res << path.trimmed();
    return res;
}

CppFileSettings CppFileSettingsWidget::settings() const
{
    CppFileSettings rc;
    rc.lowerCaseFiles = m_ui->lowerCaseFileNamesCheckBox->isChecked();
    rc.headerSuffix = m_ui->headerSuffixComboBox->currentText();
    rc.sourceSuffix = m_ui->sourceSuffixComboBox->currentText();
    rc.headerSearchPaths = trimmedPaths(m_ui->headerSearchPathsEdit->text());
    rc.sourceSearchPaths = trimmedPaths(m_ui->sourceSearchPathsEdit->text());
    rc.licenseTemplatePath = licenseTemplatePath();
    return rc;
}

QString CppFileSettingsWidget::searchKeywords() const
{
    QString rc;
    QTextStream(&rc) << m_ui->headersGroupBox->title()
            << ' ' << m_ui->headerSuffixLabel->text()
            << ' ' << m_ui->headerSearchPathsLabel->text()
            << ' ' << m_ui->sourcesGroupBox->title()
            << ' ' << m_ui->sourceSuffixLabel->text()
            << ' ' << m_ui->sourceSearchPathsLabel->text()
            << ' ' << m_ui->lowerCaseFileNamesCheckBox->text()
            << ' ' << m_ui->licenseTemplateLabel->text();
    rc.remove(QLatin1Char('&'));
    return rc;
}

static inline void setComboText(QComboBox *cb, const QString &text, int defaultIndex = 0)
{
    const int index = cb->findText(text);
    cb->setCurrentIndex(index == -1 ? defaultIndex: index);
}

void CppFileSettingsWidget::setSettings(const CppFileSettings &s)
{
    m_ui->lowerCaseFileNamesCheckBox->setChecked(s.lowerCaseFiles);
    setComboText(m_ui->headerSuffixComboBox, s.headerSuffix);
    setComboText(m_ui->sourceSuffixComboBox, s.sourceSuffix);
    m_ui->headerSearchPathsEdit->setText(s.headerSearchPaths.join(QLatin1String(",")));
    m_ui->sourceSearchPathsEdit->setText(s.sourceSearchPaths.join(QLatin1String(",")));
    setLicenseTemplatePath(s.licenseTemplatePath);
}

void CppFileSettingsWidget::slotEdit()
{
    QString path = licenseTemplatePath();
    if (path.isEmpty()) {
        // Pick a file name and write new template, edit with C++
        path = QFileDialog::getSaveFileName(this, tr("Choose Location for New License Template File"));
        if (path.isEmpty())
            return;
        Utils::FileSaver saver(path, QIODevice::Text);
        saver.write(tr(licenseTemplateTemplate).toUtf8());
        if (!saver.finalize(this))
            return;
        setLicenseTemplatePath(path);
    }
    // Edit (now) existing file with C++
    Core::EditorManager::openEditor(path, CppEditor::Constants::CPPEDITOR_ID);
}

// --------------- CppFileSettingsPage
CppFileSettingsPage::CppFileSettingsPage(QSharedPointer<CppFileSettings> &settings,
                                         QObject *parent) :
    Core::IOptionsPage(parent),
    m_settings(settings)
{
    setId(Constants::CPP_FILE_SETTINGS_ID);
    setDisplayName(QCoreApplication::translate("CppTools", Constants::CPP_FILE_SETTINGS_NAME));
    setCategory(Constants::CPP_SETTINGS_CATEGORY);
    setDisplayCategory(QCoreApplication::translate("CppTools", Constants::CPP_SETTINGS_TR_CATEGORY));
    setCategoryIcon(QLatin1String(Constants::SETTINGS_CATEGORY_CPP_ICON));
}

QWidget *CppFileSettingsPage::createPage(QWidget *parent)
{

    m_widget = new CppFileSettingsWidget(parent);
    m_widget->setSettings(*m_settings);
    if (m_searchKeywords.isEmpty())
        m_searchKeywords = m_widget->searchKeywords();
    return m_widget;
}

void CppFileSettingsPage::apply()
{
    if (m_widget) {
        const CppFileSettings newSettings = m_widget->settings();
        if (newSettings != *m_settings) {
            *m_settings = newSettings;
            m_settings->toSettings(Core::ICore::settings());
            m_settings->applySuffixesToMimeDB();
            CppToolsPlugin::clearHeaderSourceCache();
        }
    }
}

bool CppFileSettingsPage::matches(const QString &s) const
{
    return m_searchKeywords.contains(s, Qt::CaseInsensitive);
}

} // namespace Internal
} // namespace CppTools
<|endoftext|>


<commit_before>// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.

#include <vespa/eval/eval/fast_value.h>
#include <vespa/eval/eval/tensor_function.h>
#include <vespa/eval/eval/test/eval_fixture.h>
#include <vespa/eval/eval/test/tensor_model.hpp>
#include <vespa/eval/instruction/dense_tensor_peek_function.h>
#include <vespa/vespalib/testkit/test_kit.h>
#include <vespa/vespalib/util/stash.h>
#include <vespa/vespalib/util/stringfmt.h>

using namespace vespalib;
using namespace vespalib::eval;
using namespace vespalib::eval::test;
using namespace vespalib::eval::tensor_function;

const ValueBuilderFactory &prod_factory = FastValueBuilderFactory::get();

EvalFixture::ParamRepo make_params() {
    return EvalFixture::ParamRepo()
        .add("a", spec(1.0))
        .add("b", spec(2.0))
        .add("c", spec(3.0))
        .add("x3", spec(x(3), N()))
        .add("x3f", spec(float_cells({x(3)}), N()))
        .add("x3y2", spec({x(3),y(2)}, N()))
        .add("x3y2f", spec(float_cells({x(3),y(2)}), N()))
        .add("xm", spec(x({"1","2","3","-1","-2","-3"}), N()))
        .add("xmy2", spec({x({"1","2","3"}), y(2)}, N()));
}
EvalFixture::ParamRepo param_repo = make_params();

void verify(const vespalib::string &expr, double expect, size_t expect_optimized_cnt, size_t expect_not_optimized_cnt) {
    EvalFixture fixture(prod_factory, expr, param_repo, true);
    auto expect_spec = TensorSpec("double").add({}, expect);
    EXPECT_EQUAL(EvalFixture::ref(expr, param_repo), expect_spec);
    EXPECT_EQUAL(fixture.result(), expect_spec);
    auto info = fixture.find_all<DenseTensorPeekFunction>();
    EXPECT_EQUAL(info.size(), expect_optimized_cnt);
    for (size_t i = 0; i < info.size(); ++i) {
        EXPECT_TRUE(info[i]->result_is_mutable());
    }
    EXPECT_EQUAL(fixture.find_all<Peek>().size(), expect_not_optimized_cnt);
}

//-----------------------------------------------------------------------------

TEST("require that tensor peek can be optimized for dense tensors") {
    TEST_DO(verify("x3{x:0}", 1.0, 1, 0));
    TEST_DO(verify("x3{x:(a)}", 2.0, 1, 0));
    TEST_DO(verify("x3f{x:(c-1)}", 3.0, 1, 0));
    TEST_DO(verify("x3{x:(c+5)}", 0.0, 1, 0));
    TEST_DO(verify("x3{x:(a-2)}", 0.0, 1, 0));
    TEST_DO(verify("x3y2{x:(a),y:(a-1)}", 3.0, 1, 0));
    TEST_DO(verify("x3y2f{x:1,y:(a)}", 4.0, 1, 0));
    TEST_DO(verify("x3y2f{x:(a-1),y:(b)}", 0.0, 1, 0));
}

TEST("require that tensor peek is not optimized for sparse tensor") {
    TEST_DO(verify("xm{x:1}", 1.0, 0, 1));
    TEST_DO(verify("xm{x:(c)}", 3.0, 0, 1));
    TEST_DO(verify("xm{x:(c+1)}", 0.0, 0, 1));
}

TEST("require that tensor peek is not optimized for mixed tensor") {
    TEST_DO(verify("xmy2{x:3,y:1}", 6.0, 0, 1));
    TEST_DO(verify("xmy2{x:(c),y:(a)}", 6.0, 0, 1));
    TEST_DO(verify("xmy2{x:(a),y:(b)}", 0.0, 0, 1));
}

TEST("require that indexes are truncated when converted to integers") {
    TEST_DO(verify("x3{x:(a+0.7)}", 2.0, 1, 0));
    TEST_DO(verify("x3{x:(a+0.3)}", 2.0, 1, 0));
    TEST_DO(verify("xm{x:(a+0.7)}", 1.0, 0, 1));
    TEST_DO(verify("xm{x:(a+0.3)}", 1.0, 0, 1));
    TEST_DO(verify("xm{x:(-a-0.7)}", 4.0, 0, 1));
    TEST_DO(verify("xm{x:(-a-0.3)}", 4.0, 0, 1));
}

TEST_MAIN() { TEST_RUN_ALL(); }
<commit_msg>use GenSpec in dense_tensor_peek_function_test<commit_after>// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.

#include <vespa/eval/eval/fast_value.h>
#include <vespa/eval/eval/tensor_function.h>
#include <vespa/eval/eval/test/eval_fixture.h>
#include <vespa/eval/eval/test/gen_spec.h>
#include <vespa/eval/instruction/dense_tensor_peek_function.h>
#include <vespa/vespalib/testkit/test_kit.h>
#include <vespa/vespalib/util/stash.h>
#include <vespa/vespalib/util/stringfmt.h>

using namespace vespalib;
using namespace vespalib::eval;
using namespace vespalib::eval::test;
using namespace vespalib::eval::tensor_function;

const ValueBuilderFactory &prod_factory = FastValueBuilderFactory::get();

EvalFixture::ParamRepo make_params() {
    return EvalFixture::ParamRepo()
        .add("a", GenSpec().seq_bias(1.0).gen())
        .add("b", GenSpec().seq_bias(2.0).gen())
        .add("c", GenSpec().seq_bias(3.0).gen())
        .add("x3", GenSpec().idx("x", 3).gen())
        .add("x3f", GenSpec().cells_float().idx("x", 3).gen())
        .add("x3y2", GenSpec().idx("x", 3).idx("y", 2).gen())
        .add("x3y2f", GenSpec().cells_float().idx("x", 3).idx("y", 2).gen())
        .add("xm", GenSpec().map("x", {"1","2","3","-1","-2","-3"}).gen())
        .add("xmy2", GenSpec().map("x", {"1","2","3"}).idx("y", 2).gen());
}
EvalFixture::ParamRepo param_repo = make_params();

void verify(const vespalib::string &expr, double expect, size_t expect_optimized_cnt, size_t expect_not_optimized_cnt) {
    EvalFixture fixture(prod_factory, expr, param_repo, true);
    auto expect_spec = TensorSpec("double").add({}, expect);
    EXPECT_EQUAL(EvalFixture::ref(expr, param_repo), expect_spec);
    EXPECT_EQUAL(fixture.result(), expect_spec);
    auto info = fixture.find_all<DenseTensorPeekFunction>();
    EXPECT_EQUAL(info.size(), expect_optimized_cnt);
    for (size_t i = 0; i < info.size(); ++i) {
        EXPECT_TRUE(info[i]->result_is_mutable());
    }
    EXPECT_EQUAL(fixture.find_all<Peek>().size(), expect_not_optimized_cnt);
}

//-----------------------------------------------------------------------------

TEST("require that tensor peek can be optimized for dense tensors") {
    TEST_DO(verify("x3{x:0}", 1.0, 1, 0));
    TEST_DO(verify("x3{x:(a)}", 2.0, 1, 0));
    TEST_DO(verify("x3f{x:(c-1)}", 3.0, 1, 0));
    TEST_DO(verify("x3{x:(c+5)}", 0.0, 1, 0));
    TEST_DO(verify("x3{x:(a-2)}", 0.0, 1, 0));
    TEST_DO(verify("x3y2{x:(a),y:(a-1)}", 3.0, 1, 0));
    TEST_DO(verify("x3y2f{x:1,y:(a)}", 4.0, 1, 0));
    TEST_DO(verify("x3y2f{x:(a-1),y:(b)}", 0.0, 1, 0));
}

TEST("require that tensor peek is not optimized for sparse tensor") {
    TEST_DO(verify("xm{x:1}", 1.0, 0, 1));
    TEST_DO(verify("xm{x:(c)}", 3.0, 0, 1));
    TEST_DO(verify("xm{x:(c+1)}", 0.0, 0, 1));
}

TEST("require that tensor peek is not optimized for mixed tensor") {
    TEST_DO(verify("xmy2{x:3,y:1}", 6.0, 0, 1));
    TEST_DO(verify("xmy2{x:(c),y:(a)}", 6.0, 0, 1));
    TEST_DO(verify("xmy2{x:(a),y:(b)}", 0.0, 0, 1));
}

TEST("require that indexes are truncated when converted to integers") {
    TEST_DO(verify("x3{x:(a+0.7)}", 2.0, 1, 0));
    TEST_DO(verify("x3{x:(a+0.3)}", 2.0, 1, 0));
    TEST_DO(verify("xm{x:(a+0.7)}", 1.0, 0, 1));
    TEST_DO(verify("xm{x:(a+0.3)}", 1.0, 0, 1));
    TEST_DO(verify("xm{x:(-a-0.7)}", 4.0, 0, 1));
    TEST_DO(verify("xm{x:(-a-0.3)}", 4.0, 0, 1));
}

TEST_MAIN() { TEST_RUN_ALL(); }
<|endoftext|>


<commit_before>// Copyright 2015-2019 Elviss Strazdins. All rights reserved.

#include <cassert>
#include <algorithm>
#include "Camera.hpp"
#include "core/Engine.hpp"
#include "graphics/Renderer.hpp"
#include "graphics/RenderDevice.hpp"
#include "Actor.hpp"
#include "Layer.hpp"
#include "math/Matrix.hpp"

namespace ouzel
{
    namespace scene
    {
        Camera::Camera(const Matrix4F& initProjection):
            Component(CLASS),
            projectionMode(ProjectionMode::CUSTOM), projection(initProjection)

        {
            calculateViewProjection();
        }

        Camera::Camera(const Size2F& initTargetContentSize, ScaleMode initScaleMode):
            Component(CLASS),
            projectionMode(ProjectionMode::ORTHOGRAPHIC), targetContentSize(initTargetContentSize),
            scaleMode(initScaleMode)
        {
            calculateViewProjection();
        }

        Camera::Camera(float initFov, float initNearPlane, float initFarPlane):
            Component(CLASS),
            projectionMode(ProjectionMode::PERSPECTIVE), fov(initFov),
            nearPlane(initNearPlane), farPlane(initFarPlane)
        {
            calculateViewProjection();
        }

        Camera::~Camera()
        {
            if (layer) layer->removeCamera(this);
        }

        void Camera::setActor(Actor* newActor)
        {
            Component::setActor(newActor);

            viewProjectionDirty = inverseViewProjectionDirty = true;
        }

        void Camera::setLayer(Layer* newLayer)
        {
            if (layer) layer->removeCamera(this);

            Component::setLayer(newLayer);

            if (layer) layer->addCamera(this);
        }

        void Camera::updateTransform()
        {
            Component::updateTransform();

            viewProjectionDirty = inverseViewProjectionDirty = true;
        }

        void Camera::recalculateProjection()
        {
            Size2U renderTargetSize;

            if (renderTarget)
            {
                if (!renderTarget->getColorTextures().empty())
                    renderTargetSize = renderTarget->getColorTextures()[0]->getSize();
                else if (renderTarget->getDepthTexture())
                    renderTargetSize = renderTarget->getDepthTexture()->getSize();
            }
            else
                renderTargetSize = engine->getRenderer()->getSize();

            renderViewport.position.v[0] = renderTargetSize.v[0] * viewport.position.v[0];
            renderViewport.position.v[1] = renderTargetSize.v[1] * viewport.position.v[1];
            renderViewport.size.v[0] = renderTargetSize.v[0] * viewport.size.v[0];
            renderViewport.size.v[1] = renderTargetSize.v[1] * viewport.size.v[1];

            assert(renderViewport.size.v[0] > 0.0F && renderViewport.size.v[1] > 0.0F);

            if (targetContentSize.v[0] > 0.0F && targetContentSize.v[1] > 0.0F)
            {
                contentScale.v[0] = renderViewport.size.v[0] / targetContentSize.v[0];
                contentScale.v[1] = renderViewport.size.v[1] / targetContentSize.v[1];

                switch (scaleMode)
                {
                    case ScaleMode::NONE:
                    {
                        break;
                    }
                    case ScaleMode::EXACT_FIT:
                    {
                        contentScale.v[0] = 1.0F;
                        contentScale.v[1] = 1.0F;
                        break;
                    }
                    case ScaleMode::NO_BORDER:
                    {
                        contentScale.v[0] = contentScale.v[1] = std::max(contentScale.v[0], contentScale.v[1]);
                        break;
                    }
                    case ScaleMode::SHOW_ALL:
                    {
                        contentScale.v[0] = contentScale.v[1] = std::min(contentScale.v[0], contentScale.v[1]);
                        break;
                    }
                    default:
                        return;
                }

                contentSize = Size2F(renderViewport.size.v[0] / contentScale.v[0], renderViewport.size.v[1] / contentScale.v[1]);
                contentPosition = Vector2F((contentSize.v[0] - targetContentSize.v[0]) / 2.0F,
                                           (contentSize.v[1] - targetContentSize.v[1]) / 2.0F);
            }
            else
            {
                contentScale = Vector2F{1.0F, 1.0F};
                contentSize = Size2F(renderViewport.size.v[0], renderViewport.size.v[1]);
                contentPosition = Vector2F{0.0F, 0.0F};
            }

            switch (projectionMode)
            {
                case ProjectionMode::CUSTOM:
                    // do nothing
                    break;
                case ProjectionMode::ORTHOGRAPHIC:
                    projection.setOrthographicFromSize(contentSize.v[0], contentSize.v[1], -1.0F, 1.0F);
                    break;
                case ProjectionMode::PERSPECTIVE:
                    projection.setPerspective(fov, contentSize.v[0] / contentSize.v[1], nearPlane, farPlane);
                    break;
                default:
                    return;
            }

            viewProjectionDirty = inverseViewProjectionDirty = true;
        }

        const Matrix4F& Camera::getViewProjection() const
        {
            if (viewProjectionDirty) calculateViewProjection();

            return viewProjection;
        }

        const Matrix4F& Camera::getRenderViewProjection() const
        {
            if (viewProjectionDirty) calculateViewProjection();

            return renderViewProjection;
        }

        const Matrix4F& Camera::getInverseViewProjection() const
        {
            if (inverseViewProjectionDirty)
            {
                inverseViewProjection = getViewProjection();
                inverseViewProjection.invert();

                inverseViewProjectionDirty = false;
            }

            return inverseViewProjection;
        }

        void Camera::calculateViewProjection() const
        {
            if (actor)
            {
                viewProjection = projection * actor->getInverseTransform();

                renderViewProjection = engine->getRenderer()->getDevice()->getProjectionTransform(renderTarget != nullptr) * viewProjection;

                viewProjectionDirty = false;
            }
        }

        Vector3F Camera::convertNormalizedToWorld(const Vector2F& normalizedPosition) const
        {
            // convert window normalized to viewport clip position
            auto result = Vector3F{((normalizedPosition.v[0] - viewport.position.v[0]) / viewport.size.v[0] - 0.5F) * 2.0F,
                                   (((1.0F - normalizedPosition.v[1]) - viewport.position.v[1]) / viewport.size.v[1] - 0.5F) * 2.0F,
                                   0.0F};

            getInverseViewProjection().transformPoint(result);

            return result;
        }

        Vector2F Camera::convertWorldToNormalized(const Vector3F& normalizedPosition) const
        {
            Vector3F result = normalizedPosition;
            getViewProjection().transformPoint(result);

            // convert viewport clip position to window normalized
            return Vector2F((result.v[0] / 2.0F + 0.5F) * viewport.size.v[0] + viewport.position.v[0],
                            1.0F - ((result.v[1] / 2.0F + 0.5F) * viewport.size.v[1] + viewport.position.v[1]));
        }

        bool Camera::checkVisibility(const Matrix4F& boxTransform, const Box3F& box) const
        {
            if (projectionMode == ProjectionMode::ORTHOGRAPHIC)
            {
                // calculate center point of the box
                const auto diff = Vector2F(box.max - box.min);

                // offset the center point, so that it is relative to 0,0
                Vector3F v3p(box.min.v[0] + diff.v[0] / 2.0F, box.min.v[1] + diff.v[1] / 2.0F, 0.0F);

                // apply local transform to the center point
                boxTransform.transformPoint(v3p);

                // tranform the center to viewport's clip space
                Vector4F clipPos;
                getViewProjection().transformVector(Vector4F(v3p.v[0], v3p.v[1], v3p.v[2], 1.0F), clipPos);

                assert(clipPos.v[3] != 0.0F);

                // normalize position of the center point
                const Vector2F v2p((clipPos.v[0] / clipPos.v[3] + 1.0F) * 0.5F,
                                   (clipPos.v[1] / clipPos.v[3] + 1.0F) * 0.5F);

                // calculate half size
                const Size2F halfSize(diff.v[0] / 2.0F, diff.v[1] / 2.0F);

                // convert content size to world coordinates
                Size2F halfWorldSize;

                halfWorldSize.v[0] = std::max(fabs(halfSize.v[0] * boxTransform.m[0] + halfSize.v[1] * boxTransform.m[4]),
                                               fabs(halfSize.v[0] * boxTransform.m[0] - halfSize.v[1] * boxTransform.m[4]));
                halfWorldSize.v[1] = std::max(fabs(halfSize.v[0] * boxTransform.m[1] + halfSize.v[1] * boxTransform.m[5]),
                                                fabs(halfSize.v[0] * boxTransform.m[1] - halfSize.v[1] * boxTransform.m[5]));

                // scale half size by camera projection to get the size in clip space coordinates
                halfWorldSize.v[0] *= (fabs(viewProjection.m[0]) + fabs(viewProjection.m[4])) / 2.0F;
                halfWorldSize.v[1] *= (fabs(viewProjection.m[1]) + fabs(viewProjection.m[5])) / 2.0F;

                // create visible rect in clip space
                const RectF visibleRect(-halfWorldSize.v[0],
                                        -halfWorldSize.v[1],
                                        1.0F + halfWorldSize.v[0] * 2.0F,
                                        1.0F + halfWorldSize.v[1] * 2.0F);

                return visibleRect.containsPoint(v2p);
            }
            else
            {
                Matrix4F modelViewProjection = getViewProjection() * boxTransform;

                ConvexVolumeF frustum = modelViewProjection.getFrustum();
                return frustum.isBoxInside(box);
            }
        }

        void Camera::setViewport(const RectF& newViewport)
        {
            viewport = newViewport;
            recalculateProjection();
        }

        void Camera::setScaleMode(ScaleMode newScaleMode)
        {
            scaleMode = newScaleMode;
            recalculateProjection();
        }

        void Camera::setTargetContentSize(const Size2F& newTargetContentSize)
        {
            targetContentSize = newTargetContentSize;
            recalculateProjection();
        }

        void Camera::setRenderTarget(const std::shared_ptr<graphics::RenderTarget>& newRenderTarget)
        {
            renderTarget = newRenderTarget;
            recalculateProjection();
        }

        void Camera::setDepthTest(bool newDepthTest)
        {
            depthTest = newDepthTest;

            if (depthTest)
                depthStencilState = std::make_shared<graphics::DepthStencilState>(*engine->getRenderer(),
                                                                                true, true,
                                                                                graphics::CompareFunction::LessEqual,
                                                                                false,
                                                                                0xFFFFFFFF,
                                                                                0xFFFFFFFF,
                                                                                graphics::StencilOperation::Keep,
                                                                                graphics::StencilOperation::Keep,
                                                                                graphics::StencilOperation::Keep,
                                                                                graphics::CompareFunction::AlwaysPass,
                                                                                graphics::StencilOperation::Keep,
                                                                                graphics::StencilOperation::Keep,
                                                                                graphics::StencilOperation::Keep,
                                                                                graphics::CompareFunction::AlwaysPass);
            else
                depthStencilState.reset();
        }
    } // namespace scene
} // namespace ouzel
<commit_msg>Fix the Camera code<commit_after>// Copyright 2015-2019 Elviss Strazdins. All rights reserved.

#include <cassert>
#include <algorithm>
#include "Camera.hpp"
#include "core/Engine.hpp"
#include "graphics/Renderer.hpp"
#include "graphics/RenderDevice.hpp"
#include "Actor.hpp"
#include "Layer.hpp"
#include "math/Matrix.hpp"

namespace ouzel
{
    namespace scene
    {
        Camera::Camera(const Matrix4F& initProjection):
            Component(CLASS),
            projectionMode(ProjectionMode::CUSTOM), projection(initProjection)

        {
            calculateViewProjection();
        }

        Camera::Camera(const Size2F& initTargetContentSize, ScaleMode initScaleMode):
            Component(CLASS),
            projectionMode(ProjectionMode::ORTHOGRAPHIC), targetContentSize(initTargetContentSize),
            scaleMode(initScaleMode)
        {
            calculateViewProjection();
        }

        Camera::Camera(float initFov, float initNearPlane, float initFarPlane):
            Component(CLASS),
            projectionMode(ProjectionMode::PERSPECTIVE), fov(initFov),
            nearPlane(initNearPlane), farPlane(initFarPlane)
        {
            calculateViewProjection();
        }

        Camera::~Camera()
        {
            if (layer) layer->removeCamera(this);
        }

        void Camera::setActor(Actor* newActor)
        {
            Component::setActor(newActor);

            viewProjectionDirty = inverseViewProjectionDirty = true;
        }

        void Camera::setLayer(Layer* newLayer)
        {
            if (layer) layer->removeCamera(this);

            Component::setLayer(newLayer);

            if (layer) layer->addCamera(this);
        }

        void Camera::updateTransform()
        {
            Component::updateTransform();

            viewProjectionDirty = inverseViewProjectionDirty = true;
        }

        void Camera::recalculateProjection()
        {
            Size2U renderTargetSize;

            if (renderTarget)
            {
                if (!renderTarget->getColorTextures().empty())
                    renderTargetSize = renderTarget->getColorTextures()[0]->getSize();
                else if (renderTarget->getDepthTexture())
                    renderTargetSize = renderTarget->getDepthTexture()->getSize();
            }
            else
                renderTargetSize = engine->getRenderer()->getSize();

            renderViewport.position.v[0] = renderTargetSize.v[0] * viewport.position.v[0];
            renderViewport.position.v[1] = renderTargetSize.v[1] * viewport.position.v[1];
            renderViewport.size.v[0] = renderTargetSize.v[0] * viewport.size.v[0];
            renderViewport.size.v[1] = renderTargetSize.v[1] * viewport.size.v[1];

            assert(renderViewport.size.v[0] > 0.0F && renderViewport.size.v[1] > 0.0F);

            if (targetContentSize.v[0] > 0.0F && targetContentSize.v[1] > 0.0F)
            {
                contentScale.v[0] = renderViewport.size.v[0] / targetContentSize.v[0];
                contentScale.v[1] = renderViewport.size.v[1] / targetContentSize.v[1];

                switch (scaleMode)
                {
                    case ScaleMode::NONE:
                    {
                        break;
                    }
                    case ScaleMode::EXACT_FIT:
                    {
                        contentScale.v[0] = 1.0F;
                        contentScale.v[1] = 1.0F;
                        break;
                    }
                    case ScaleMode::NO_BORDER:
                    {
                        contentScale.v[0] = contentScale.v[1] = std::max(contentScale.v[0], contentScale.v[1]);
                        break;
                    }
                    case ScaleMode::SHOW_ALL:
                    {
                        contentScale.v[0] = contentScale.v[1] = std::min(contentScale.v[0], contentScale.v[1]);
                        break;
                    }
                    default:
                        return;
                }

                contentSize = Size2F(renderViewport.size.v[0] / contentScale.v[0], renderViewport.size.v[1] / contentScale.v[1]);
                contentPosition = Vector2F((contentSize.v[0] - targetContentSize.v[0]) / 2.0F,
                                           (contentSize.v[1] - targetContentSize.v[1]) / 2.0F);
            }
            else
            {
                contentScale = Vector2F{1.0F, 1.0F};
                contentSize = Size2F(renderViewport.size.v[0], renderViewport.size.v[1]);
                contentPosition = Vector2F{0.0F, 0.0F};
            }

            switch (projectionMode)
            {
                case ProjectionMode::CUSTOM:
                    // do nothing
                    break;
                case ProjectionMode::ORTHOGRAPHIC:
                    projection.setOrthographicFromSize(contentSize.v[0], contentSize.v[1], -1.0F, 1.0F);
                    break;
                case ProjectionMode::PERSPECTIVE:
                    projection.setPerspective(fov, contentSize.v[0] / contentSize.v[1], nearPlane, farPlane);
                    break;
                default:
                    return;
            }

            viewProjectionDirty = inverseViewProjectionDirty = true;
        }

        const Matrix4F& Camera::getViewProjection() const
        {
            if (viewProjectionDirty) calculateViewProjection();

            return viewProjection;
        }

        const Matrix4F& Camera::getRenderViewProjection() const
        {
            if (viewProjectionDirty) calculateViewProjection();

            return renderViewProjection;
        }

        const Matrix4F& Camera::getInverseViewProjection() const
        {
            if (inverseViewProjectionDirty)
            {
                inverseViewProjection = getViewProjection();
                inverseViewProjection.invert();

                inverseViewProjectionDirty = false;
            }

            return inverseViewProjection;
        }

        void Camera::calculateViewProjection() const
        {
            if (actor)
            {
                viewProjection = projection * actor->getInverseTransform();

                renderViewProjection = engine->getRenderer()->getDevice()->getProjectionTransform(renderTarget != nullptr) * viewProjection;

                viewProjectionDirty = false;
            }
        }

        Vector3F Camera::convertNormalizedToWorld(const Vector2F& normalizedPosition) const
        {
            // convert window normalized to viewport clip position
            auto result = Vector3F{((normalizedPosition.v[0] - viewport.position.v[0]) / viewport.size.v[0] - 0.5F) * 2.0F,
                                   (((1.0F - normalizedPosition.v[1]) - viewport.position.v[1]) / viewport.size.v[1] - 0.5F) * 2.0F,
                                   0.0F};

            getInverseViewProjection().transformPoint(result);

            return result;
        }

        Vector2F Camera::convertWorldToNormalized(const Vector3F& normalizedPosition) const
        {
            Vector3F result = normalizedPosition;
            getViewProjection().transformPoint(result);

            // convert viewport clip position to window normalized
            return Vector2F((result.v[0] / 2.0F + 0.5F) * viewport.size.v[0] + viewport.position.v[0],
                            1.0F - ((result.v[1] / 2.0F + 0.5F) * viewport.size.v[1] + viewport.position.v[1]));
        }

        bool Camera::checkVisibility(const Matrix4F& boxTransform, const Box3F& box) const
        {
            if (projectionMode == ProjectionMode::ORTHOGRAPHIC)
            {
                // calculate center point of the box
                const auto diff = Vector2F(box.max - box.min);

                // offset the center point, so that it is relative to 0,0
                Vector3F v3p(box.min.v[0] + diff.v[0] / 2.0F, box.min.v[1] + diff.v[1] / 2.0F, 0.0F);

                // apply local transform to the center point
                boxTransform.transformPoint(v3p);

                // tranform the center to viewport's clip space
                Vector4F clipPos;
                getViewProjection().transformVector(Vector4F(v3p.v[0], v3p.v[1], v3p.v[2], 1.0F), clipPos);

                assert(clipPos.v[3] != 0.0F);

                // normalize position of the center point
                const Vector2F v2p((clipPos.v[0] / clipPos.v[3] + 1.0F) * 0.5F,
                                   (clipPos.v[1] / clipPos.v[3] + 1.0F) * 0.5F);

                // calculate half size
                const Size2F halfSize(diff.v[0] / 2.0F, diff.v[1] / 2.0F);

                // convert content size to world coordinates
                Size2F halfWorldSize;

                halfWorldSize.v[0] = std::max(fabs(halfSize.v[0] * boxTransform.m[0] + halfSize.v[1] * boxTransform.m[4]),
                                               fabs(halfSize.v[0] * boxTransform.m[0] - halfSize.v[1] * boxTransform.m[4]));
                halfWorldSize.v[1] = std::max(fabs(halfSize.v[0] * boxTransform.m[1] + halfSize.v[1] * boxTransform.m[5]),
                                                fabs(halfSize.v[0] * boxTransform.m[1] - halfSize.v[1] * boxTransform.m[5]));

                // scale half size by camera projection to get the size in clip space coordinates
                halfWorldSize.v[0] *= (fabs(viewProjection.m[0]) + fabs(viewProjection.m[4])) / 2.0F;
                halfWorldSize.v[1] *= (fabs(viewProjection.m[1]) + fabs(viewProjection.m[5])) / 2.0F;

                // create visible rect in clip space
                const RectF visibleRect(-halfWorldSize.v[0],
                                        -halfWorldSize.v[1],
                                        1.0F + halfWorldSize.v[0] * 2.0F,
                                        1.0F + halfWorldSize.v[1] * 2.0F);

                return visibleRect.containsPoint(v2p);
            }
            else
            {
                Matrix4F modelViewProjection = getViewProjection() * boxTransform;

                ConvexVolumeF frustum = modelViewProjection.getFrustum();
                return frustum.isBoxInside(box);
            }
        }

        void Camera::setViewport(const RectF& newViewport)
        {
            viewport = newViewport;
            recalculateProjection();
        }

        void Camera::setScaleMode(ScaleMode newScaleMode)
        {
            scaleMode = newScaleMode;
            recalculateProjection();
        }

        void Camera::setTargetContentSize(const Size2F& newTargetContentSize)
        {
            targetContentSize = newTargetContentSize;
            recalculateProjection();
        }

        void Camera::setRenderTarget(const std::shared_ptr<graphics::RenderTarget>& newRenderTarget)
        {
            renderTarget = newRenderTarget;
            recalculateProjection();
        }

        void Camera::setDepthTest(bool newDepthTest)
        {
            depthTest = newDepthTest;

            if (depthTest)
                depthStencilState = std::make_shared<graphics::DepthStencilState>(*engine->getRenderer(),
                                                                                true, true,
                                                                                graphics::CompareFunction::PassIfLessEqual,
                                                                                false,
                                                                                0xFFFFFFFF,
                                                                                0xFFFFFFFF,
                                                                                graphics::StencilOperation::Keep,
                                                                                graphics::StencilOperation::Keep,
                                                                                graphics::StencilOperation::Keep,
                                                                                graphics::CompareFunction::AlwaysPass,
                                                                                graphics::StencilOperation::Keep,
                                                                                graphics::StencilOperation::Keep,
                                                                                graphics::StencilOperation::Keep,
                                                                                graphics::CompareFunction::AlwaysPass);
            else
                depthStencilState.reset();
        }
    } // namespace scene
} // namespace ouzel
<|endoftext|>


<commit_before>// Copyright 2008 Google Inc.
// All Rights Reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Author: vladl@google.com (Vlad Losev)

// This sample shows how to test common properties of multiple
// implementations of an interface (aka interface tests) using
// value-parameterized tests. Each test in the test case has
// a parameter that is an interface pointer to an implementation
// tested.

// The interface and its implementations are in this header.
#include "prime_tables.h"

#include "gtest/gtest.h"

#if GTEST_HAS_PARAM_TEST

using ::testing::TestWithParam;
using ::testing::Values;

// As a general rule, tested objects should not be reused between tests.
// Also, their constructors and destructors of tested objects can have
// side effects. Thus you should create and destroy them for each test.
// In this sample we will define a simple factory function for PrimeTable
// objects. We will instantiate objects in test's SetUp() method and
// delete them in TearDown() method.
typedef PrimeTable* CreatePrimeTableFunc();

PrimeTable* CreateOnTheFlyPrimeTable() {
  return new OnTheFlyPrimeTable();
}

template <size_t max_precalculated>
PrimeTable* CreatePreCalculatedPrimeTable() {
  return new PreCalculatedPrimeTable(max_precalculated);
}

// Inside the test body, fixture constructor, SetUp(), and TearDown()
// you can refer to the test parameter by GetParam().
// In this case, the test parameter is a PrimeTableFactory interface pointer
// which we use in fixture's SetUp() to create and store an instance of
// PrimeTable.
class PrimeTableTest : public TestWithParam<CreatePrimeTableFunc*> {
 public:
  virtual ~PrimeTableTest() { delete table_; }
  virtual void SetUp() { table_ = (*GetParam())(); }
  virtual void TearDown() {
    delete table_;
    table_ = NULL;
  }

 protected:
  PrimeTable* table_;
};

TEST_P(PrimeTableTest, ReturnsFalseForNonPrimes) {
  EXPECT_FALSE(table_->IsPrime(-5));
  EXPECT_FALSE(table_->IsPrime(0));
  EXPECT_FALSE(table_->IsPrime(1));
  EXPECT_FALSE(table_->IsPrime(4));
  EXPECT_FALSE(table_->IsPrime(6));
  EXPECT_FALSE(table_->IsPrime(100));
}

TEST_P(PrimeTableTest, ReturnsTrueForPrimes) {
  EXPECT_TRUE(table_->IsPrime(2));
  EXPECT_TRUE(table_->IsPrime(3));
  EXPECT_TRUE(table_->IsPrime(5));
  EXPECT_TRUE(table_->IsPrime(7));
  EXPECT_TRUE(table_->IsPrime(11));
  EXPECT_TRUE(table_->IsPrime(131));
}

TEST_P(PrimeTableTest, CanGetNextPrime) {
  EXPECT_EQ(2, table_->GetNextPrime(0));
  EXPECT_EQ(3, table_->GetNextPrime(2));
  EXPECT_EQ(5, table_->GetNextPrime(3));
  EXPECT_EQ(7, table_->GetNextPrime(5));
  EXPECT_EQ(11, table_->GetNextPrime(7));
  EXPECT_EQ(131, table_->GetNextPrime(128));
}

// In order to run value-parameterized tests, you need to instantiate them,
// or bind them to a list of values which will be used as test parameters.
// You can instantiate them in a different translation module, or even
// instantiate them several times.
//
// Here, we instantiate our tests with a list of two PrimeTable object
// factory functions:
INSTANTIATE_TEST_CASE_P(
    OnTheFlyAndPreCalculated,
    PrimeTableTest,
    Values(&CreateOnTheFlyPrimeTable, &CreatePreCalculatedPrimeTable<1000>));

#else

// Google Test may not support value-parameterized tests with some
// compilers. If we use conditional compilation to compile out all
// code referring to the gtest_main library, MSVC linker will not link
// that library at all and consequently complain about missing entry
// point defined in that library (fatal error LNK1561: entry point
// must be defined). This dummy test keeps gtest_main linked in.
TEST(DummyTest, ValueParameterizedTestsAreNotSupportedOnThisPlatform) {}

#endif  // GTEST_HAS_PARAM_TEST
<commit_msg>Fixes comments in sample7_unittest.cc.<commit_after>// Copyright 2008 Google Inc.
// All Rights Reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Author: vladl@google.com (Vlad Losev)

// This sample shows how to test common properties of multiple
// implementations of an interface (aka interface tests) using
// value-parameterized tests. Each test in the test case has
// a parameter that is an interface pointer to an implementation
// tested.

// The interface and its implementations are in this header.
#include "prime_tables.h"

#include "gtest/gtest.h"

#if GTEST_HAS_PARAM_TEST

using ::testing::TestWithParam;
using ::testing::Values;

// As a general rule, to prevent a test from affecting the tests that come
// after it, you should create and destroy the tested objects for each test
// instead of reusing them.  In this sample we will define a simple factory
// function for PrimeTable objects.  We will instantiate objects in test's
// SetUp() method and delete them in TearDown() method.
typedef PrimeTable* CreatePrimeTableFunc();

PrimeTable* CreateOnTheFlyPrimeTable() {
  return new OnTheFlyPrimeTable();
}

template <size_t max_precalculated>
PrimeTable* CreatePreCalculatedPrimeTable() {
  return new PreCalculatedPrimeTable(max_precalculated);
}

// Inside the test body, fixture constructor, SetUp(), and TearDown() you
// can refer to the test parameter by GetParam().  In this case, the test
// parameter is a factory function which we call in fixture's SetUp() to
// create and store an instance of PrimeTable.
class PrimeTableTest : public TestWithParam<CreatePrimeTableFunc*> {
 public:
  virtual ~PrimeTableTest() { delete table_; }
  virtual void SetUp() { table_ = (*GetParam())(); }
  virtual void TearDown() {
    delete table_;
    table_ = NULL;
  }

 protected:
  PrimeTable* table_;
};

TEST_P(PrimeTableTest, ReturnsFalseForNonPrimes) {
  EXPECT_FALSE(table_->IsPrime(-5));
  EXPECT_FALSE(table_->IsPrime(0));
  EXPECT_FALSE(table_->IsPrime(1));
  EXPECT_FALSE(table_->IsPrime(4));
  EXPECT_FALSE(table_->IsPrime(6));
  EXPECT_FALSE(table_->IsPrime(100));
}

TEST_P(PrimeTableTest, ReturnsTrueForPrimes) {
  EXPECT_TRUE(table_->IsPrime(2));
  EXPECT_TRUE(table_->IsPrime(3));
  EXPECT_TRUE(table_->IsPrime(5));
  EXPECT_TRUE(table_->IsPrime(7));
  EXPECT_TRUE(table_->IsPrime(11));
  EXPECT_TRUE(table_->IsPrime(131));
}

TEST_P(PrimeTableTest, CanGetNextPrime) {
  EXPECT_EQ(2, table_->GetNextPrime(0));
  EXPECT_EQ(3, table_->GetNextPrime(2));
  EXPECT_EQ(5, table_->GetNextPrime(3));
  EXPECT_EQ(7, table_->GetNextPrime(5));
  EXPECT_EQ(11, table_->GetNextPrime(7));
  EXPECT_EQ(131, table_->GetNextPrime(128));
}

// In order to run value-parameterized tests, you need to instantiate them,
// or bind them to a list of values which will be used as test parameters.
// You can instantiate them in a different translation module, or even
// instantiate them several times.
//
// Here, we instantiate our tests with a list of two PrimeTable object
// factory functions:
INSTANTIATE_TEST_CASE_P(
    OnTheFlyAndPreCalculated,
    PrimeTableTest,
    Values(&CreateOnTheFlyPrimeTable, &CreatePreCalculatedPrimeTable<1000>));

#else

// Google Test may not support value-parameterized tests with some
// compilers. If we use conditional compilation to compile out all
// code referring to the gtest_main library, MSVC linker will not link
// that library at all and consequently complain about missing entry
// point defined in that library (fatal error LNK1561: entry point
// must be defined). This dummy test keeps gtest_main linked in.
TEST(DummyTest, ValueParameterizedTestsAreNotSupportedOnThisPlatform) {}

#endif  // GTEST_HAS_PARAM_TEST
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2017 Love Park Robotics, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distribted on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <chrono>
#include <cstdint>
#include <exception>
#include <fstream>
#include <iostream>
#include <memory>
#include <ratio>
#include <string>
#include <vector>
#include <boost/program_options.hpp>
#include <o3d3xx_camera.h>
#include <o3d3xx_oem.h>
#include <opencv2/opencv.hpp>

//
// Quantify framegrabber jitter
//

namespace po = boost::program_options;

template<typename T>
T median2d(const cv::Mat& arr)
{
  T median = 0;
  std::size_t sz = arr.rows*arr.cols;

  std::vector<T> arr_cp(sz);
  std::copy(arr.begin<T>(), arr.end<T>(), arr_cp.begin());
  std::nth_element(arr_cp.begin(), arr_cp.begin() + sz/2, arr_cp.end());

  if (sz > 0)
    {
      if (sz % 2 == 0)
        {
          median =
            (arr_cp.at(sz/2-1)+arr_cp.at(sz/2))/2.;
        }
      else
        {
          median = arr_cp.at(sz/2);
        }
    }

  return median;
}

template<typename T>
T mad(const cv::Mat& arr, T center)
{
  cv::Mat arr_d;
  cv::absdiff(arr, center, arr_d);
  return median2d<T>(arr_d);
}

int main(int argc, const char** argv)
{
  std::string camera_ip;
  std::uint32_t xmlrpc_port;
  std::string password;
  int nframes;
  std::string outfile;
  cv::Mat arr;

  try
    {
      o3d3xx::CmdLineOpts opts("o3d3xx OEM Frame Grabber Jitter");

      po::options_description jitter_opts("Jitter");
      jitter_opts.add_options()
        ("nframes,f",
         po::value<int>()->default_value(100),
         "Number of frames to capture")

        ("outfile,o",
         po::value<std::string>()->default_value("-"),
         "Raw data output file, if not specified, nothing is written");

      opts.visible.add(jitter_opts);

      if (! opts.Parse(argc, argv, &camera_ip, &xmlrpc_port, &password))
        {
          return 0;
        }

      outfile = opts.vm["outfile"].as<std::string>();
      nframes = opts.vm["nframes"].as<int>();
      nframes = nframes <= 0 ? 100 : nframes;

      auto cam =
        std::make_shared<o3d3xx::Camera>(camera_ip, xmlrpc_port, password);
      auto fg = std::make_shared<o3d3xx::oem::FrameGrabber>(cam);
      auto im = std::make_shared<o3d3xx::oem::ImageBuffer>();
      // get the one-time allocations out of the way, and,
      // make sure it doesn't get optimized away by the compiler
      if (! fg->WaitForFrame(im.get(), 1000))
        {
          std::cerr << "Timeout waiting for first image acquisition!"
                    << std::endl;
          return 1;
        }

      // holds our timing results (millis)
      arr.create(nframes, 1, CV_32FC1);

      for (int i = 0; i < nframes; ++i)
        {
          auto t1 = std::chrono::high_resolution_clock::now();
          if (! fg->WaitForFrame(im.get(), 1000))
            {
              std::cerr << "Timeout waiting for image acquisition!"
                        << std::endl;
              return 1;
            }
          auto t2 = std::chrono::high_resolution_clock::now();

          std::chrono::duration<float, std::milli> fp_ms = t2 - t1;
          arr.at<float>(i, 0) = fp_ms.count();
        }

      cv::Scalar mean, stddev;
      float median = median2d<float>(arr);
      cv::meanStdDev(arr, mean, stddev);
      std::cout << "Mean:   " << mean[0] << " ms" << std::endl;
      std::cout << "Median: " <<  median << " ms" << std::endl;
      std::cout << "Stddev: " << stddev[0] << " ms" << std::endl;
      std::cout << "MAD:    " << mad(arr, median) << " ms" << std::endl;

      if (outfile != "-")
        {
          std::ofstream out;
          out.open(outfile);
          out << cv::format(arr, cv::Formatter::FMT_CSV);
          out.close();
          std::cout << "Raw data has been written to: " << outfile << std::endl;
        }
    }
  catch (const std::exception& ex)
    {
      std::cerr << "Failed to compute jitter statistics: "
                << ex.what() << std::endl;
      return 1;
    }

  return 0;
}
<commit_msg>o3d3xx-oem-jitter had a bug in median computation for when the number of captured frames was even -- invalid assumptions on the partial sorting algorithm.<commit_after>/*
 * Copyright (C) 2017 Love Park Robotics, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distribted on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <chrono>
#include <cstdint>
#include <exception>
#include <fstream>
#include <iostream>
#include <memory>
#include <ratio>
#include <string>
#include <vector>
#include <boost/program_options.hpp>
#include <o3d3xx_camera.h>
#include <o3d3xx_oem.h>
#include <opencv2/opencv.hpp>

//
// Quantify framegrabber jitter
//

namespace po = boost::program_options;

template<typename T>
T median2d(const cv::Mat& arr)
{
  T median = 0;
  std::size_t sz = arr.rows*arr.cols;

  std::vector<T> arr_cp(sz);
  std::copy(arr.begin<T>(), arr.end<T>(), arr_cp.begin());
  std::sort(arr_cp.begin(), arr_cp.end());

  if (sz > 0)
    {
      if (sz % 2 == 0)
        {
          median =
            (arr_cp.at(sz/2-1)+arr_cp.at(sz/2))/2.;
        }
      else
        {
          median = arr_cp.at(sz/2);
        }
    }

  return median;
}

template<typename T>
T mad(const cv::Mat& arr, T center)
{
  cv::Mat arr_d;
  cv::absdiff(arr, center, arr_d);
  return median2d<T>(arr_d);
}

int main(int argc, const char** argv)
{
  std::string camera_ip;
  std::uint32_t xmlrpc_port;
  std::string password;
  int nframes;
  std::string outfile;
  cv::Mat arr;

  try
    {
      o3d3xx::CmdLineOpts opts("o3d3xx OEM Frame Grabber Jitter");

      po::options_description jitter_opts("Jitter");
      jitter_opts.add_options()
        ("nframes,f",
         po::value<int>()->default_value(100),
         "Number of frames to capture")

        ("outfile,o",
         po::value<std::string>()->default_value("-"),
         "Raw data output file, if not specified, nothing is written");

      opts.visible.add(jitter_opts);

      if (! opts.Parse(argc, argv, &camera_ip, &xmlrpc_port, &password))
        {
          return 0;
        }

      outfile = opts.vm["outfile"].as<std::string>();
      nframes = opts.vm["nframes"].as<int>();
      nframes = nframes <= 0 ? 100 : nframes;

      auto cam =
        std::make_shared<o3d3xx::Camera>(camera_ip, xmlrpc_port, password);
      auto fg = std::make_shared<o3d3xx::oem::FrameGrabber>(cam);
      auto im = std::make_shared<o3d3xx::oem::ImageBuffer>();
      // get the one-time allocations out of the way, and,
      // make sure it doesn't get optimized away by the compiler
      if (! fg->WaitForFrame(im.get(), 1000))
        {
          std::cerr << "Timeout waiting for first image acquisition!"
                    << std::endl;
          return 1;
        }

      // holds our timing results (millis)
      arr.create(nframes, 1, CV_32FC1);

      for (int i = 0; i < nframes; ++i)
        {
          auto t1 = std::chrono::high_resolution_clock::now();
          if (! fg->WaitForFrame(im.get(), 1000))
            {
              std::cerr << "Timeout waiting for image acquisition!"
                        << std::endl;
              return 1;
            }
          auto t2 = std::chrono::high_resolution_clock::now();

          std::chrono::duration<float, std::milli> fp_ms = t2 - t1;
          arr.at<float>(i, 0) = fp_ms.count();
        }

      cv::Scalar mean, stddev;
      float median = median2d<float>(arr);
      cv::meanStdDev(arr, mean, stddev);
      std::cout << "Mean:   " << mean[0] << " ms" << std::endl;
      std::cout << "Median: " <<  median << " ms" << std::endl;
      std::cout << "Stddev: " << stddev[0] << " ms" << std::endl;
      std::cout << "MAD:    " << mad(arr, median) << " ms" << std::endl;

      if (outfile != "-")
        {
          std::ofstream out;
          out.open(outfile);
          out << cv::format(arr, cv::Formatter::FMT_CSV);
          out.close();
          std::cout << "Raw data has been written to: " << outfile << std::endl;
        }
    }
  catch (const std::exception& ex)
    {
      std::cerr << "Failed to compute jitter statistics: "
                << ex.what() << std::endl;
      return 1;
    }

  return 0;
}
<|endoftext|>