<commit_before>/* This file is part of Zanshin

   Copyright 2014 Kevin Ottens <ervin@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 2 of
   the License or (at your option) version 3 or any later version
   accepted by the membership of KDE e.V. (or its successor approved
   by the membership of KDE e.V.), which shall act as a proxy
   defined in Section 14 of version 3 of the license.

   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, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
   USA.
*/

#include "applicationmodel.h"

#include "domain/artifactqueries.h"
#include "domain/noterepository.h"
#include "domain/taskqueries.h"
#include "domain/taskrepository.h"

#include "presentation/datasourcelistmodel.h"
#include "presentation/querytreemodel.h"
#include "presentation/inboxpagemodel.h"

#include "utils/dependencymanager.h"

using namespace Presentation;

ApplicationModel::ApplicationModel(QObject *parent)
    : QObject(parent),
      m_currentPage(0),
      m_artifactQueries(Utils::DependencyManager::globalInstance().create<Domain::ArtifactQueries>()),
      m_sourceQueries(Utils::DependencyManager::globalInstance().create<Domain::DataSourceQueries>()),
      m_taskQueries(Utils::DependencyManager::globalInstance().create<Domain::TaskQueries>()),
      m_taskRepository(Utils::DependencyManager::globalInstance().create<Domain::TaskRepository>()),
      m_taskSourcesModel(0),
      m_noteRepository(Utils::DependencyManager::globalInstance().create<Domain::NoteRepository>()),
      m_noteSourcesModel(0),
      m_ownInterface(true)
{
    qRegisterMetaType<Domain::DataSource::Ptr>();
}

ApplicationModel::ApplicationModel(Domain::ArtifactQueries *artifactQueries,
                       Domain::DataSourceQueries *sourceQueries,
                       Domain::TaskQueries *taskQueries,
                       Domain::TaskRepository *taskRepository,
                       Domain::NoteRepository *noteRepository,
                       QObject *parent)
    : QObject(parent),
      m_currentPage(0),
      m_artifactQueries(artifactQueries),
      m_sourceQueries(sourceQueries),
      m_taskQueries(taskQueries),
      m_taskRepository(taskRepository),
      m_taskSourcesModel(0),
      m_noteRepository(noteRepository),
      m_noteSourcesModel(0),
      m_ownInterface(false)
{
    qRegisterMetaType<Domain::DataSource::Ptr>();
}

ApplicationModel::~ApplicationModel()
{
}

QAbstractItemModel *ApplicationModel::noteSourcesModel()
{
    if (!m_noteSourcesModel) {
        m_noteSourcesModel = new DataSourceListModel([this] { return noteSources(); }, this);
    }

    return m_noteSourcesModel;
}

Domain::QueryResult<Domain::DataSource::Ptr>::Ptr ApplicationModel::noteSources()
{
    if (!m_noteSources) {
        m_noteSources = m_sourceQueries->findNotes();
    }

    return m_noteSources;
}

Domain::DataSource::Ptr ApplicationModel::defaultNoteDataSource()
{
    QList<Domain::DataSource::Ptr> sources = noteSources()->data();

    if (sources.isEmpty())
        return Domain::DataSource::Ptr();

    auto source = std::find_if(sources.begin(), sources.end(),
                               [this] (const Domain::DataSource::Ptr &source) {
                                   return m_noteRepository->isDefaultSource(source);
                               });

    if (source != sources.end())
        return *source;
    else
        return sources.first();
}

QAbstractItemModel *ApplicationModel::taskSourcesModel()
{
    if (!m_taskSourcesModel) {
        m_taskSourcesModel = new DataSourceListModel([this] { return taskSources(); }, this);
    }

    return m_taskSourcesModel;
}

Domain::QueryResult<Domain::DataSource::Ptr>::Ptr ApplicationModel::taskSources()
{
    if (!m_taskSources) {
        m_taskSources = m_sourceQueries->findTasks();
    }

    return m_taskSources;
}

Domain::DataSource::Ptr ApplicationModel::defaultTaskDataSource()
{
    QList<Domain::DataSource::Ptr> sources = taskSources()->data();

    if (sources.isEmpty())
        return Domain::DataSource::Ptr();

    auto source = std::find_if(sources.begin(), sources.end(),
                               [this] (const Domain::DataSource::Ptr &source) {
                                   return m_taskRepository->isDefaultSource(source);
                               });

    if (source != sources.end())
        return *source;
    else
        return sources.first();
}

QObject *ApplicationModel::currentPage()
{
    if (!m_currentPage) {
        m_currentPage = new InboxPageModel(m_artifactQueries, m_taskQueries,
                                           m_taskRepository, m_noteRepository);
    }

    return m_currentPage;
}

void ApplicationModel::setDefaultNoteDataSource(Domain::DataSource::Ptr source)
{
    m_noteRepository->setDefaultSource(source);
}

void ApplicationModel::setDefaultTaskDataSource(Domain::DataSource::Ptr source)
{
    m_taskRepository->setDefaultSource(source);
}
<commit_msg>Add missing register metatype<commit_after>/* This file is part of Zanshin

   Copyright 2014 Kevin Ottens <ervin@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 2 of
   the License or (at your option) version 3 or any later version
   accepted by the membership of KDE e.V. (or its successor approved
   by the membership of KDE e.V.), which shall act as a proxy
   defined in Section 14 of version 3 of the license.

   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, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
   USA.
*/

#include "applicationmodel.h"

#include "domain/artifactqueries.h"
#include "domain/noterepository.h"
#include "domain/taskqueries.h"
#include "domain/taskrepository.h"

#include "presentation/datasourcelistmodel.h"
#include "presentation/querytreemodel.h"
#include "presentation/inboxpagemodel.h"

#include "utils/dependencymanager.h"

using namespace Presentation;

ApplicationModel::ApplicationModel(QObject *parent)
    : QObject(parent),
      m_currentPage(0),
      m_artifactQueries(Utils::DependencyManager::globalInstance().create<Domain::ArtifactQueries>()),
      m_sourceQueries(Utils::DependencyManager::globalInstance().create<Domain::DataSourceQueries>()),
      m_taskQueries(Utils::DependencyManager::globalInstance().create<Domain::TaskQueries>()),
      m_taskRepository(Utils::DependencyManager::globalInstance().create<Domain::TaskRepository>()),
      m_taskSourcesModel(0),
      m_noteRepository(Utils::DependencyManager::globalInstance().create<Domain::NoteRepository>()),
      m_noteSourcesModel(0),
      m_ownInterface(true)
{
    qRegisterMetaType<QAbstractItemModel*>();
    qRegisterMetaType<Domain::DataSource::Ptr>();
}

ApplicationModel::ApplicationModel(Domain::ArtifactQueries *artifactQueries,
                       Domain::DataSourceQueries *sourceQueries,
                       Domain::TaskQueries *taskQueries,
                       Domain::TaskRepository *taskRepository,
                       Domain::NoteRepository *noteRepository,
                       QObject *parent)
    : QObject(parent),
      m_currentPage(0),
      m_artifactQueries(artifactQueries),
      m_sourceQueries(sourceQueries),
      m_taskQueries(taskQueries),
      m_taskRepository(taskRepository),
      m_taskSourcesModel(0),
      m_noteRepository(noteRepository),
      m_noteSourcesModel(0),
      m_ownInterface(false)
{
    qRegisterMetaType<QAbstractItemModel*>();
    qRegisterMetaType<Domain::DataSource::Ptr>();
}

ApplicationModel::~ApplicationModel()
{
}

QAbstractItemModel *ApplicationModel::noteSourcesModel()
{
    if (!m_noteSourcesModel) {
        m_noteSourcesModel = new DataSourceListModel([this] { return noteSources(); }, this);
    }

    return m_noteSourcesModel;
}

Domain::QueryResult<Domain::DataSource::Ptr>::Ptr ApplicationModel::noteSources()
{
    if (!m_noteSources) {
        m_noteSources = m_sourceQueries->findNotes();
    }

    return m_noteSources;
}

Domain::DataSource::Ptr ApplicationModel::defaultNoteDataSource()
{
    QList<Domain::DataSource::Ptr> sources = noteSources()->data();

    if (sources.isEmpty())
        return Domain::DataSource::Ptr();

    auto source = std::find_if(sources.begin(), sources.end(),
                               [this] (const Domain::DataSource::Ptr &source) {
                                   return m_noteRepository->isDefaultSource(source);
                               });

    if (source != sources.end())
        return *source;
    else
        return sources.first();
}

QAbstractItemModel *ApplicationModel::taskSourcesModel()
{
    if (!m_taskSourcesModel) {
        m_taskSourcesModel = new DataSourceListModel([this] { return taskSources(); }, this);
    }

    return m_taskSourcesModel;
}

Domain::QueryResult<Domain::DataSource::Ptr>::Ptr ApplicationModel::taskSources()
{
    if (!m_taskSources) {
        m_taskSources = m_sourceQueries->findTasks();
    }

    return m_taskSources;
}

Domain::DataSource::Ptr ApplicationModel::defaultTaskDataSource()
{
    QList<Domain::DataSource::Ptr> sources = taskSources()->data();

    if (sources.isEmpty())
        return Domain::DataSource::Ptr();

    auto source = std::find_if(sources.begin(), sources.end(),
                               [this] (const Domain::DataSource::Ptr &source) {
                                   return m_taskRepository->isDefaultSource(source);
                               });

    if (source != sources.end())
        return *source;
    else
        return sources.first();
}

QObject *ApplicationModel::currentPage()
{
    if (!m_currentPage) {
        m_currentPage = new InboxPageModel(m_artifactQueries, m_taskQueries,
                                           m_taskRepository, m_noteRepository);
    }

    return m_currentPage;
}

void ApplicationModel::setDefaultNoteDataSource(Domain::DataSource::Ptr source)
{
    m_noteRepository->setDefaultSource(source);
}

void ApplicationModel::setDefaultTaskDataSource(Domain::DataSource::Ptr source)
{
    m_taskRepository->setDefaultSource(source);
}
<|endoftext|>


<commit_before>#include "ui_dock.h"
#include "ui_dock_private.h"
#include "api/include/pd_view.h"
#include "core/plugin_handler.h"
#include "core/alloc.h"
#include "core/log.h"
#include "../plugin.h"
#include <jansson.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void assignIds(UIDockingGrid* grid)
{
    int index = 4;

    grid->topSizer.id = 0;
    grid->bottomSizer.id = 1;
    grid->rightSizer.id = 2;
    grid->leftSizer.id = 3;

    for (UIDockSizer* sizer : grid->sizers)
        sizer->id = index++;

    index = 0;

    for (UIDock* dock : grid->docks)
        dock->id = index++;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void writeSizer(UIDockSizer* sizer, json_t* root, double xScale, double yScale)
{
    json_t* sizerItem = json_pack("{s:i, s:i, s:f, s:f, s:f, s:f}",
                                  "dir", (int)sizer->dir,
                                  "id", (int)sizer->id,
                                  "x",  double(sizer->rect.x) * xScale,
                                  "y", double(sizer->rect.y) * yScale,
                                  "width", double(sizer->rect.width) * xScale,
                                  "height", double(sizer->rect.height) * yScale);

    json_t* consArray = json_array();

    for (UIDock* dock : sizer->cons)
    {
        json_t* consItem = json_integer(dock->id);
        json_array_append_new(consArray, consItem);
    }

    json_object_set_new(sizerItem, "cons", consArray );

    json_array_append_new(root, sizerItem);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void writeSizers(UIDockingGrid* grid, json_t* root, double xScale, double yScale)
{
    json_t* sizersArray = json_array();

    writeSizer(&grid->topSizer, sizersArray, xScale, yScale);
    writeSizer(&grid->bottomSizer, sizersArray, xScale, yScale);
    writeSizer(&grid->rightSizer, sizersArray, xScale, yScale);
    writeSizer(&grid->leftSizer, sizersArray, xScale, yScale);

    for (UIDockSizer* sizer : grid->sizers)
        writeSizer(sizer, sizersArray, xScale, yScale);

    json_object_set_new(root, "sizers", sizersArray);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void writeDocks(UIDockingGrid* grid, json_t* root, double xScale, double yScale)
{
    json_t* docksArray = json_array();

    for (UIDock* dock : grid->docks)
    {
        const char* pluginName = "";
        const char* filename = "";

        if (dock->view->plugin)
        {
            PluginData* pluginData = PluginHandler_getPluginData(dock->view->plugin);

            pluginName = dock->view->plugin->name;
            filename = pluginData->filename;
        }

        json_t* dockItem = json_pack("{s:s, s:s, s:i, s:i, s:f, s:f, s:f, s:f, s:i, s:i, s:i, s:i}",
                                     "plugin_name", pluginName,
                                     "plugin_file", filename,
                                     "type", (int)dock->type,
                                     "id", dock->id,
                                     "x",  double(dock->view->rect.x) * xScale,
                                     "y", double(dock->view->rect.y) * yScale,
                                     "width", double(dock->view->rect.width) * xScale,
                                     "height", double(dock->view->rect.height) * yScale,
                                     "s0", dock->sizers[0]->id,
                                     "s1", dock->sizers[1]->id,
                                     "s2", dock->sizers[2]->id,
                                     "s3", dock->sizers[3]->id);

        json_array_append_new(docksArray, dockItem);
    }

    json_object_set_new(root, "docks", docksArray);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool UIDock_saveLayout(UIDockingGrid* grid, const char* filename, float xScale, float yScale)
{
    json_t* root = json_object();

	UIDock_saveLayoutJson(grid, root, xScale, yScale);

    if (json_dump_file(root, filename, JSON_INDENT(4) | JSON_PRESERVE_ORDER) != 0)
    {
        pd_error("JSON: Unable to open %s for write\n", filename);
        return false;
    }

    return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool UIDock_saveLayoutJson(UIDockingGrid* grid, json_t* root, float xScale, float yScale)
{
    double invScaleX = 1.0 / xScale;
    double invScaleY = 1.0 / yScale;

    assignIds(grid);

    writeSizers(grid, root, invScaleX, invScaleY);
    writeDocks(grid, root, invScaleX, invScaleY);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static UIDockSizer* findSizer(UIDockingGrid* grid, const int id)
{
    switch (id)
    {
        case 0:
            return &grid->topSizer;
        case 1:
            return &grid->bottomSizer;
        case 2:
            return &grid->rightSizer;
        case 3:
            return &grid->leftSizer;
    }

    for (UIDockSizer* sizer : grid->sizers)
    {
        if (sizer->id == id)
            return sizer;
    }

    return nullptr;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void loadDocks(UIDockingGrid* grid, json_t* root, double xScale, double yScale)
{
    const size_t count = json_array_size(root);

    grid->docks.reserve(count);

    for (size_t i = 0; i < count; ++i)
    {
        double x, y, width, height;
        const char* pluginName;
        const char* filename;
        int id, type;
        int ids[4];

        json_t* item = json_array_get(root, i);

        json_unpack(item, "{s:s, s:s, s:i, s:i, s:f, s:f, s:f, s:f, s:i, s:i, s:i, s:i}",
                    "plugin_name", &pluginName,
                    "plugin_file", &filename,
                    "type", &type,
                    "id", &id,
                    "x", &x,
                    "y", &y,
                    "width", &width,
                    "height", &height,
                    "s0", &ids[0],
                    "s1", &ids[1],
                    "s2", &ids[2],
                    "s3", &ids[3]);

        IntRect rect = {{{ int(x * xScale), int(y * yScale), int(width * xScale), int(height * yScale) }}};

        ViewPluginInstance* view = 0;

        // if this is the case we have no plugin created (empty window)

        if (!strcmp(pluginName, "") && !strcmp(filename, ""))
        {
            view = (ViewPluginInstance*)alloc_zero(sizeof(ViewPluginInstance));
        }
        else
        {
            PluginData* pluginData = PluginHandler_findPlugin(0, filename, pluginName, true);

            if (!pluginData)
                view = (ViewPluginInstance*)alloc_zero(sizeof(ViewPluginInstance));
            else
                view = g_pluginUI->createViewPlugin(pluginData);
        }

        assert(view);

        UIDock* dock = new UIDock(view);

        for (size_t k = 0; k < UIDock::Sizers_Count; ++k)
            dock->sizers[k] = findSizer(grid, ids[k]);

        dock->view->rect = rect;
        dock->type = (UIDockType)type;
        dock->id = id;

        grid->docks.push_back(dock);
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void loadSizers(UIDockingGrid* grid, json_t* root, double xScale, double yScale)
{
    const size_t count = json_array_size(root);

    for (size_t i = 0; i < count; ++i)
    {
        double x, y, width, height;
        int dir, id;

        json_t* item = json_array_get(root, i);

        json_unpack(item, "{s:i, s:i, s:f, s:f, s:f, s:f}",
                    "dir", &dir,
                    "id", &id,
                    "x", &x,
                    "y", &y,
                    "width", &width,
                    "height", &height);

        IntRect rect = {{{ int(x * xScale), int(y * yScale), int(width * xScale), int(height * yScale) }}};

        UIDockSizer* sizer = 0;

        // ID range 0 - 3 = border sizers

        switch (id)
        {
            case UIDock::Top:
            {
                sizer = &grid->topSizer;
                break;
            }

            case UIDock::Bottom:
            {
                sizer = &grid->bottomSizer;
                break;
            }

            case UIDock::Right:
            {
                sizer = &grid->rightSizer;
                break;
            }

            case UIDock::Left:
            {
                sizer = &grid->leftSizer;
                break;
            }

            default:
            {
                sizer = new UIDockSizer;
                grid->sizers.push_back(sizer);
            }
        }

        sizer->dir = (UIDockSizerDir)dir;
        sizer->id = id;
        sizer->rect = rect;

        json_t* consArray = json_object_get(item, "cons");

        if (consArray && json_is_array(consArray))
        {
            const size_t consCount = json_array_size(consArray);

            for (size_t k = 0; k < consCount; ++k)
            {
                json_t* value = json_array_get(consArray, k);
                int res = (int)json_integer_value(value);
                sizer->dockIds.push_back(res);
            }
        }
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static UIDock* findDock(UIDockingGrid* grid, const int id)
{
    for (UIDock* dock : grid->docks)
    {
        if (dock->id == id)
            return dock;
    }

    assert(false);

    return nullptr;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void mapSizer(UIDockingGrid* grid, UIDockSizer* sizer)
{
    for (int i : sizer->dockIds)
        sizer->addDock(findDock(grid, i));
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void mapDockSizers(UIDockingGrid* grid)
{
    mapSizer(grid, &grid->topSizer);
    mapSizer(grid, &grid->bottomSizer);
    mapSizer(grid, &grid->rightSizer);
    mapSizer(grid, &grid->leftSizer);

    for (UIDockSizer* sizer : grid->sizers)
        mapSizer(grid, sizer);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

UIDockingGrid* UIDock_loadLayout(const char* filename, float xSize, float ySize)
{
    UIDockingGrid* grid = 0;
    json_error_t error;

    json_t* root = json_load_file(filename, 0, &error);

    if (!root || !json_is_object(root))
    {
        pd_error("JSON: Unable to open %s for read\n", filename);
        return 0;
    }

	return UIDock_loadLayoutJson(root, xSize, ySize);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

UIDockingGrid* UIDock_loadLayoutJson(struct json_t* root, float xSize, float ySize)
{
    UIDockingGrid* grid = 0;

    IntRect rect = {{{ 0, 0, (int)xSize, (int)ySize }}};

    grid = UIDock_createGrid(&rect);

    // Load sizers

    json_t* sizers = json_object_get(root, "sizers");

    if (!sizers || !json_is_array(sizers))
    {
        pd_error("JSON: Unable to load sizers object\n");
        UIDock_destroyGrid(grid);
        return 0;
    }

    loadSizers(grid, sizers, xSize, ySize);

    json_t* docks = json_object_get(root, "docks");

    if (!docks || !json_is_array(docks))
    {
        pd_error("JSON: Unable to load docks object\n");
        UIDock_destroyGrid(grid);
        return 0;
    }

    loadDocks(grid, docks, xSize, ySize);

    // map up the docks with the sizers

    mapDockSizers(grid);

    return grid;
}

<commit_msg>Another win32 fix<commit_after>#include "ui_dock.h"
#include "ui_dock_private.h"
#include "api/include/pd_view.h"
#include "core/plugin_handler.h"
#include "core/alloc.h"
#include "core/log.h"
#include "../plugin.h"
#include <jansson.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void assignIds(UIDockingGrid* grid)
{
    int index = 4;

    grid->topSizer.id = 0;
    grid->bottomSizer.id = 1;
    grid->rightSizer.id = 2;
    grid->leftSizer.id = 3;

    for (UIDockSizer* sizer : grid->sizers)
        sizer->id = index++;

    index = 0;

    for (UIDock* dock : grid->docks)
        dock->id = index++;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void writeSizer(UIDockSizer* sizer, json_t* root, double xScale, double yScale)
{
    json_t* sizerItem = json_pack("{s:i, s:i, s:f, s:f, s:f, s:f}",
                                  "dir", (int)sizer->dir,
                                  "id", (int)sizer->id,
                                  "x",  double(sizer->rect.x) * xScale,
                                  "y", double(sizer->rect.y) * yScale,
                                  "width", double(sizer->rect.width) * xScale,
                                  "height", double(sizer->rect.height) * yScale);

    json_t* consArray = json_array();

    for (UIDock* dock : sizer->cons)
    {
        json_t* consItem = json_integer(dock->id);
        json_array_append_new(consArray, consItem);
    }

    json_object_set_new(sizerItem, "cons", consArray );

    json_array_append_new(root, sizerItem);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void writeSizers(UIDockingGrid* grid, json_t* root, double xScale, double yScale)
{
    json_t* sizersArray = json_array();

    writeSizer(&grid->topSizer, sizersArray, xScale, yScale);
    writeSizer(&grid->bottomSizer, sizersArray, xScale, yScale);
    writeSizer(&grid->rightSizer, sizersArray, xScale, yScale);
    writeSizer(&grid->leftSizer, sizersArray, xScale, yScale);

    for (UIDockSizer* sizer : grid->sizers)
        writeSizer(sizer, sizersArray, xScale, yScale);

    json_object_set_new(root, "sizers", sizersArray);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void writeDocks(UIDockingGrid* grid, json_t* root, double xScale, double yScale)
{
    json_t* docksArray = json_array();

    for (UIDock* dock : grid->docks)
    {
        const char* pluginName = "";
        const char* filename = "";

        if (dock->view->plugin)
        {
            PluginData* pluginData = PluginHandler_getPluginData(dock->view->plugin);

            pluginName = dock->view->plugin->name;
            filename = pluginData->filename;
        }

        json_t* dockItem = json_pack("{s:s, s:s, s:i, s:i, s:f, s:f, s:f, s:f, s:i, s:i, s:i, s:i}",
                                     "plugin_name", pluginName,
                                     "plugin_file", filename,
                                     "type", (int)dock->type,
                                     "id", dock->id,
                                     "x",  double(dock->view->rect.x) * xScale,
                                     "y", double(dock->view->rect.y) * yScale,
                                     "width", double(dock->view->rect.width) * xScale,
                                     "height", double(dock->view->rect.height) * yScale,
                                     "s0", dock->sizers[0]->id,
                                     "s1", dock->sizers[1]->id,
                                     "s2", dock->sizers[2]->id,
                                     "s3", dock->sizers[3]->id);

        json_array_append_new(docksArray, dockItem);
    }

    json_object_set_new(root, "docks", docksArray);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool UIDock_saveLayout(UIDockingGrid* grid, const char* filename, float xScale, float yScale)
{
    json_t* root = json_object();

	UIDock_saveLayoutJson(grid, root, xScale, yScale);

    if (json_dump_file(root, filename, JSON_INDENT(4) | JSON_PRESERVE_ORDER) != 0)
    {
        pd_error("JSON: Unable to open %s for write\n", filename);
        return false;
    }

    return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool UIDock_saveLayoutJson(UIDockingGrid* grid, json_t* root, float xScale, float yScale)
{
    double invScaleX = 1.0 / xScale;
    double invScaleY = 1.0 / yScale;

    assignIds(grid);

    writeSizers(grid, root, invScaleX, invScaleY);
    writeDocks(grid, root, invScaleX, invScaleY);

    return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static UIDockSizer* findSizer(UIDockingGrid* grid, const int id)
{
    switch (id)
    {
        case 0:
            return &grid->topSizer;
        case 1:
            return &grid->bottomSizer;
        case 2:
            return &grid->rightSizer;
        case 3:
            return &grid->leftSizer;
    }

    for (UIDockSizer* sizer : grid->sizers)
    {
        if (sizer->id == id)
            return sizer;
    }

    return nullptr;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void loadDocks(UIDockingGrid* grid, json_t* root, double xScale, double yScale)
{
    const size_t count = json_array_size(root);

    grid->docks.reserve(count);

    for (size_t i = 0; i < count; ++i)
    {
        double x, y, width, height;
        const char* pluginName;
        const char* filename;
        int id, type;
        int ids[4];

        json_t* item = json_array_get(root, i);

        json_unpack(item, "{s:s, s:s, s:i, s:i, s:f, s:f, s:f, s:f, s:i, s:i, s:i, s:i}",
                    "plugin_name", &pluginName,
                    "plugin_file", &filename,
                    "type", &type,
                    "id", &id,
                    "x", &x,
                    "y", &y,
                    "width", &width,
                    "height", &height,
                    "s0", &ids[0],
                    "s1", &ids[1],
                    "s2", &ids[2],
                    "s3", &ids[3]);

        IntRect rect = {{{ int(x * xScale), int(y * yScale), int(width * xScale), int(height * yScale) }}};

        ViewPluginInstance* view = 0;

        // if this is the case we have no plugin created (empty window)

        if (!strcmp(pluginName, "") && !strcmp(filename, ""))
        {
            view = (ViewPluginInstance*)alloc_zero(sizeof(ViewPluginInstance));
        }
        else
        {
            PluginData* pluginData = PluginHandler_findPlugin(0, filename, pluginName, true);

            if (!pluginData)
                view = (ViewPluginInstance*)alloc_zero(sizeof(ViewPluginInstance));
            else
                view = g_pluginUI->createViewPlugin(pluginData);
        }

        assert(view);

        UIDock* dock = new UIDock(view);

        for (size_t k = 0; k < UIDock::Sizers_Count; ++k)
            dock->sizers[k] = findSizer(grid, ids[k]);

        dock->view->rect = rect;
        dock->type = (UIDockType)type;
        dock->id = id;

        grid->docks.push_back(dock);
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void loadSizers(UIDockingGrid* grid, json_t* root, double xScale, double yScale)
{
    const size_t count = json_array_size(root);

    for (size_t i = 0; i < count; ++i)
    {
        double x, y, width, height;
        int dir, id;

        json_t* item = json_array_get(root, i);

        json_unpack(item, "{s:i, s:i, s:f, s:f, s:f, s:f}",
                    "dir", &dir,
                    "id", &id,
                    "x", &x,
                    "y", &y,
                    "width", &width,
                    "height", &height);

        IntRect rect = {{{ int(x * xScale), int(y * yScale), int(width * xScale), int(height * yScale) }}};

        UIDockSizer* sizer = 0;

        // ID range 0 - 3 = border sizers

        switch (id)
        {
            case UIDock::Top:
            {
                sizer = &grid->topSizer;
                break;
            }

            case UIDock::Bottom:
            {
                sizer = &grid->bottomSizer;
                break;
            }

            case UIDock::Right:
            {
                sizer = &grid->rightSizer;
                break;
            }

            case UIDock::Left:
            {
                sizer = &grid->leftSizer;
                break;
            }

            default:
            {
                sizer = new UIDockSizer;
                grid->sizers.push_back(sizer);
            }
        }

        sizer->dir = (UIDockSizerDir)dir;
        sizer->id = id;
        sizer->rect = rect;

        json_t* consArray = json_object_get(item, "cons");

        if (consArray && json_is_array(consArray))
        {
            const size_t consCount = json_array_size(consArray);

            for (size_t k = 0; k < consCount; ++k)
            {
                json_t* value = json_array_get(consArray, k);
                int res = (int)json_integer_value(value);
                sizer->dockIds.push_back(res);
            }
        }
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static UIDock* findDock(UIDockingGrid* grid, const int id)
{
    for (UIDock* dock : grid->docks)
    {
        if (dock->id == id)
            return dock;
    }

    assert(false);

    return nullptr;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void mapSizer(UIDockingGrid* grid, UIDockSizer* sizer)
{
    for (int i : sizer->dockIds)
        sizer->addDock(findDock(grid, i));
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static void mapDockSizers(UIDockingGrid* grid)
{
    mapSizer(grid, &grid->topSizer);
    mapSizer(grid, &grid->bottomSizer);
    mapSizer(grid, &grid->rightSizer);
    mapSizer(grid, &grid->leftSizer);

    for (UIDockSizer* sizer : grid->sizers)
        mapSizer(grid, sizer);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

UIDockingGrid* UIDock_loadLayout(const char* filename, float xSize, float ySize)
{
    UIDockingGrid* grid = 0;
    json_error_t error;

    json_t* root = json_load_file(filename, 0, &error);

    if (!root || !json_is_object(root))
    {
        pd_error("JSON: Unable to open %s for read\n", filename);
        return 0;
    }

	return UIDock_loadLayoutJson(root, xSize, ySize);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

UIDockingGrid* UIDock_loadLayoutJson(struct json_t* root, float xSize, float ySize)
{
    UIDockingGrid* grid = 0;

    IntRect rect = {{{ 0, 0, (int)xSize, (int)ySize }}};

    grid = UIDock_createGrid(&rect);

    // Load sizers

    json_t* sizers = json_object_get(root, "sizers");

    if (!sizers || !json_is_array(sizers))
    {
        pd_error("JSON: Unable to load sizers object\n");
        UIDock_destroyGrid(grid);
        return 0;
    }

    loadSizers(grid, sizers, xSize, ySize);

    json_t* docks = json_object_get(root, "docks");

    if (!docks || !json_is_array(docks))
    {
        pd_error("JSON: Unable to load docks object\n");
        UIDock_destroyGrid(grid);
        return 0;
    }

    loadDocks(grid, docks, xSize, ySize);

    // map up the docks with the sizers

    mapDockSizers(grid);

    return grid;
}

<|endoftext|>


<commit_before>#include <okui/views/TextView.h>

#include <okui/shapes/Rectangle.h>
#include <okui/Window.h>

#include <utf8/utf8.h>

#include <cmath>

namespace okui::views {

double TextView::lineHeight() const {
    return _font ? _font->lineSpacing() * _fontScale() : 0;
}

Point<int> TextView::lineColumnPosition(size_t lineNum, size_t col) const {
    if (!_font) { return{0, 0}; }

    auto fontScale = _fontScale();
    auto lineSpacing = _font->lineSpacing() * fontScale;

    auto& line = _lines[lineNum];
    auto textWidth = _lineWidth({line.data(), std::min(col, line.size())}, fontScale);

    return {static_cast<int>(round(_calcXOffset(line) + textWidth)),
            static_cast<int>(round(_calcYOffset() + (lineNum * lineSpacing)))};
}

std::pair<size_t, size_t> TextView::lineColumnAtPosition(int mouseX, int mouseY) const {
    if (!_font || _lines.empty()) { return{0, 0}; }

    auto fontScale   = _fontScale();
    auto lineSpacing = _font->lineSpacing() * fontScale;

    auto lineNum = std::min<size_t>(_lines.size()-1, std::max(0, static_cast<int>((mouseY-_calcYOffset()) / lineSpacing)));
    auto& line = _lines[lineNum];

    double x = _calcXOffset(line);

    for (size_t i = 0; i < line.size(); ++i) {
        if (i > 0) {
            x += _font->kerning(line[i - 1], line[i]) * fontScale + _style.letterSpacing();
        }
        auto glyph = _font->glyph(line[i]);
        if (glyph) {
            x += glyph->xAdvance * fontScale;
        }

        if (mouseX < x) {
            return {lineNum, i};
        }
    }

    return {lineNum, line.size()};
}

void TextView::setText(stdts::string_view text) {
    if (_text == text) {
        return;
    }
    _text = std::string(text);

    _glyphs.clear();

    uint32_t codePoint;
    uint32_t state = 0;

    for (auto c : _text) {
        if (utf8::Decode(&state, &codePoint, static_cast<unsigned char>(c))) {
            continue;
        }
        _glyphs += codePoint;
    }

    _updateLines();
}

void TextView::setStyle(Style style) {
    _style = std::move(style);
    _updateLines();
}

void TextView::setFont(std::string texture, std::string metadata) {
    _style.font(std::move(texture), std::move(metadata));
    _font = nullptr;
    _textWidth = 0;

    if (window()) {
        _font = window()->loadBitmapFontResource(_style.fontTexture().c_str(), _style.fontMetadata().c_str());
        _updateLines();
    }
}

void TextView::setTextSize(double size) {
    _style.textSize(size);
    _updateLines();
}

void TextView::setLetterSpacing(double letterSpacing) {
    _style.letterSpacing(letterSpacing);
    _updateLines();
}

void TextView::setLetterSpacingFromTracking(double tracking) {
    _style.letterSpacingFromTracking(tracking);
    _updateLines();
}

void TextView::setAlignment(Style::HorizontalAlignment alignment) {
    _style.alignment(alignment);
    invalidateRenderCache();
}

void TextView::setAlignment(Style::VerticalAlignment alignment) {
    _style.alignment(alignment);
    invalidateRenderCache();
}

void TextView::setAlignment(Style::HorizontalAlignment horizontal, Style::VerticalAlignment vertical) {
    _style.alignment(horizontal, vertical);
    invalidateRenderCache();
}

void TextView::setOverflowBehavior(Style::OverflowBehavior overflowBehavior) {
    _style.overflowBehavior(overflowBehavior);
    _updateLines();
}

void TextView::setWeight(double weight) {
    _style.weight(weight);
    invalidateRenderCache();
}

void TextView::setEllipsesEnabled(bool enabled) {
    _style.ellipsesEnabled(enabled);
    invalidateRenderCache();
}

void TextView::setTextColor(Color color) {
    _style.textColor(std::move(color));
    invalidateRenderCache();
}

void TextView::setOutline(double outline) {
    _style.outline(outline);
    invalidateRenderCache();
}

void TextView::setOutlineColor(Color color) {
    _style.outlineColor(std::move(color));
    invalidateRenderCache();
}

void TextView::render(const RenderTarget* renderTarget, const Rectangle<int>& area) {
    if (!_font) { return; }

    auto distanceFieldShader = this->distanceFieldShader();
    distanceFieldShader->enableSupersampling(true);

    std::vector<shaders::DistanceFieldShader::Region> regions;
    const auto fillEdge = std::min(std::max(1.0 - _style.weight() / 200.0, 0.0), 1.0);
    if (_style.outline()) {
        regions.emplace_back(1.0, _style.outlineColor(), fillEdge - (1.0 - fillEdge) * _style.outline() / 100.0, _style.outlineColor());
    }
    regions.emplace_back(1.0, _style.textColor(), fillEdge, _style.textColor());
    distanceFieldShader->setRegions(std::move(regions));

    distanceFieldShader->setColor(Color::kWhite);
    _renderBitmapText(distanceFieldShader);

    distanceFieldShader->flush();
}

void TextView::layout() {
    _updateLines();
}

void TextView::windowChanged() {
    if (window() && !_style.fontTexture().empty() && !_style.fontMetadata().empty()) {
        _font = window()->loadBitmapFontResource(_style.fontTexture().c_str(), _style.fontMetadata().c_str());
        _updateLines();
    }
}

std::vector<std::basic_string<BitmapFont::GlyphId>> TextView::_computeLines(double width, double height) const {
    std::vector<std::basic_string<BitmapFont::GlyphId>> lines;
    if (!_font) { return lines; }

    auto fontScale = _style.textSize() / _font->size();

    std::basic_string<BitmapFont::GlyphId> line;

    static constexpr std::array<BitmapFont::GlyphId, 3> ellipses{{ 0x2e, 0x2e, 0x2e }};
    auto ellipsesWidth = _lineWidth({ellipses.data(), ellipses.size()}, fontScale);
    bool skipUntilNewLine = false;
    auto linesToShow = std::max<size_t>(1, floor(height / lineHeight()));
    auto useEllipses = _style.ellipsesEnabled() && _style.overflowBehavior() != Style::OverflowBehavior::kShrink;

    for (auto& glyph : _glyphs) {
        bool needsEllipses = false;

        if (glyph == '\n') {
            if (lines.size() + 1 >= linesToShow && useEllipses) {
                needsEllipses = true;
            } else {
                lines.emplace_back(std::move(line));
                line.clear();
                skipUntilNewLine = false;
                continue;
            }
        } else if (skipUntilNewLine) {
            continue;
        } else {
            line.push_back(glyph);

            if (_lineWidth(line, fontScale) > width) {
                if (_style.overflowBehavior() == Style::OverflowBehavior::kWrap && lines.size() + 1 < linesToShow) {
                    bool didBreak = false;
                    // try to break
                    for (size_t i = line.size(); i > 0; --i) {
                        auto& candidate = line[i - 1];
                        auto glyph = _font->glyph(candidate);
                        if (glyph && !glyph->width) {
                            // whitespace
                            if (i > 0) {
                                lines.emplace_back(&line[0], i - 1);
                            }
                            if (i < line.size()) {
                                line = line.substr(i);
                            } else {
                                line.clear();
                            }
                            didBreak = true;
                            break;
                        }
                    }
                    if (!didBreak && !line.empty()) {
                        // just put this character on a new line
                        auto& last = line.back();
                        line.pop_back();
                        lines.emplace_back(std::move(line));
                        line.clear();
                        line += last;
                    }
                } else if (useEllipses) {
                    needsEllipses = true;
                }
            }
        }

        if (needsEllipses) {
            // rewind until the ellipses fit
            do {
                line.pop_back();
            } while (!line.empty() && _lineWidth({line.data(), line.size()}, fontScale) + ellipsesWidth > width);
            // trim whitespace
            while (!line.empty()) {
                auto glyph = _font->glyph(line.back());
                if (!glyph || glyph->width) {
                    break;
                }
                line.pop_back();
            }
            // add the ellipses
            line.insert(line.end(), ellipses.begin(), ellipses.end());
            if (lines.size() >= linesToShow) {
                break;
            }
            skipUntilNewLine = true;
        }
    }

    if (!line.empty()) {
        lines.emplace_back(std::move(line));
    }

    return lines;
}

void TextView::_updateLines() {
    if (!_font) { return; }

    _lines = _computeLines(bounds().width, bounds().height);

    _textWidth = 0.0;
    auto fontScale = _style.textSize() / _font->size();
    for (auto& line : _lines) {
        _textWidth = std::max(_textWidth, _lineWidth({line.data(), line.size()}, fontScale));
    }

    invalidateRenderCache();
}

void TextView::_renderBitmapText(shaders::DistanceFieldShader* shader) {
    // TODO: this could be much more optimized (e.g. via display list or vbo)

    auto& texture = _font->texture();
    if (!texture->isLoaded()) { return; }

    auto fontScale = _fontScale();
    auto lineSpacing = _font->lineSpacing() * fontScale;
    auto y = _calcYOffset();

    for (auto& line : _lines) {
        double x = _calcXOffset(line);

        for (size_t i = 0; i < line.size(); ++i) {
            if (i > 0) {
                x += _font->kerning(line[i - 1], line[i]) * fontScale + _style.letterSpacing();
            }
            auto glyph = _font->glyph(line[i]);
            if (glyph) {
                Rectangle<double> glyphBounds(x + glyph->xOffset * fontScale, y + glyph->yOffset * fontScale, glyph->width * fontScale, glyph->height * fontScale);
                auto textureScale = glyphBounds.width / glyph->textureWidth;
                shader->setTexture(
                    *texture,
                    glyphBounds.x - glyph->textureX * textureScale,
                    glyphBounds.y - glyph->textureY * textureScale,
                    texture->width() * textureScale,
                    texture->height() * textureScale
                );
                shapes::Rectangle(glyphBounds.x, glyphBounds.y, glyphBounds.width, glyphBounds.height).draw(shader);
                x += glyph->xAdvance * fontScale;
            }
        }

        y += lineSpacing;
    }
}

double TextView::_calcXOffset(const std::basic_string<BitmapFont::GlyphId>& line) const {
    if (!_font) { return 0; }

    auto textWidth = _lineWidth(line, _fontScale());

    double x = 0.0;
    if (_style.horizontalAlignment() == Style::HorizontalAlignment::kCenter) {
        x = (bounds().width - textWidth) * 0.5;
    } else if (_style.horizontalAlignment() == Style::HorizontalAlignment::kRight) {
        x = bounds().width - textWidth;
    }

    return x;
}

double TextView::_calcYOffset() const {
    if (!_font) { return 0; }

    auto y = 0.0;
    if (_style.verticalAlignment() == Style::VerticalAlignment::kMiddle) {
        y = (bounds().height - textHeight()) * 0.5;
    } else if (_style.verticalAlignment() == Style::VerticalAlignment::kBottom) {
        y = bounds().height - textHeight();
    }

    return y;
}

double TextView::_fontScale() const {
    if (_style.overflowBehavior() == Style::OverflowBehavior::kShrink && _textWidth > bounds().width) {
        return _style.textSize() / _font->size() * bounds().width / _textWidth;
    }
    return _style.textSize() / _font->size();
}

double TextView::_lineWidth(stdts::basic_string_view<BitmapFont::GlyphId> line, double fontScale) const {
    auto letterSpacing = line.empty() ? 0 : (line.size()-1) * _style.letterSpacing();
    return _font->width(line.data(), line.size()) * fontScale + letterSpacing;
}

} // namespace okui::views
<commit_msg>fix: crash with ellipses on empty line<commit_after>#include <okui/views/TextView.h>

#include <okui/shapes/Rectangle.h>
#include <okui/Window.h>

#include <utf8/utf8.h>

#include <cmath>

namespace okui::views {

double TextView::lineHeight() const {
    return _font ? _font->lineSpacing() * _fontScale() : 0;
}

Point<int> TextView::lineColumnPosition(size_t lineNum, size_t col) const {
    if (!_font) { return{0, 0}; }

    auto fontScale = _fontScale();
    auto lineSpacing = _font->lineSpacing() * fontScale;

    auto& line = _lines[lineNum];
    auto textWidth = _lineWidth({line.data(), std::min(col, line.size())}, fontScale);

    return {static_cast<int>(round(_calcXOffset(line) + textWidth)),
            static_cast<int>(round(_calcYOffset() + (lineNum * lineSpacing)))};
}

std::pair<size_t, size_t> TextView::lineColumnAtPosition(int mouseX, int mouseY) const {
    if (!_font || _lines.empty()) { return{0, 0}; }

    auto fontScale   = _fontScale();
    auto lineSpacing = _font->lineSpacing() * fontScale;

    auto lineNum = std::min<size_t>(_lines.size()-1, std::max(0, static_cast<int>((mouseY-_calcYOffset()) / lineSpacing)));
    auto& line = _lines[lineNum];

    double x = _calcXOffset(line);

    for (size_t i = 0; i < line.size(); ++i) {
        if (i > 0) {
            x += _font->kerning(line[i - 1], line[i]) * fontScale + _style.letterSpacing();
        }
        auto glyph = _font->glyph(line[i]);
        if (glyph) {
            x += glyph->xAdvance * fontScale;
        }

        if (mouseX < x) {
            return {lineNum, i};
        }
    }

    return {lineNum, line.size()};
}

void TextView::setText(stdts::string_view text) {
    if (_text == text) {
        return;
    }
    _text = std::string(text);

    _glyphs.clear();

    uint32_t codePoint;
    uint32_t state = 0;

    for (auto c : _text) {
        if (utf8::Decode(&state, &codePoint, static_cast<unsigned char>(c))) {
            continue;
        }
        _glyphs += codePoint;
    }

    _updateLines();
}

void TextView::setStyle(Style style) {
    _style = std::move(style);
    _updateLines();
}

void TextView::setFont(std::string texture, std::string metadata) {
    _style.font(std::move(texture), std::move(metadata));
    _font = nullptr;
    _textWidth = 0;

    if (window()) {
        _font = window()->loadBitmapFontResource(_style.fontTexture().c_str(), _style.fontMetadata().c_str());
        _updateLines();
    }
}

void TextView::setTextSize(double size) {
    _style.textSize(size);
    _updateLines();
}

void TextView::setLetterSpacing(double letterSpacing) {
    _style.letterSpacing(letterSpacing);
    _updateLines();
}

void TextView::setLetterSpacingFromTracking(double tracking) {
    _style.letterSpacingFromTracking(tracking);
    _updateLines();
}

void TextView::setAlignment(Style::HorizontalAlignment alignment) {
    _style.alignment(alignment);
    invalidateRenderCache();
}

void TextView::setAlignment(Style::VerticalAlignment alignment) {
    _style.alignment(alignment);
    invalidateRenderCache();
}

void TextView::setAlignment(Style::HorizontalAlignment horizontal, Style::VerticalAlignment vertical) {
    _style.alignment(horizontal, vertical);
    invalidateRenderCache();
}

void TextView::setOverflowBehavior(Style::OverflowBehavior overflowBehavior) {
    _style.overflowBehavior(overflowBehavior);
    _updateLines();
}

void TextView::setWeight(double weight) {
    _style.weight(weight);
    invalidateRenderCache();
}

void TextView::setEllipsesEnabled(bool enabled) {
    _style.ellipsesEnabled(enabled);
    invalidateRenderCache();
}

void TextView::setTextColor(Color color) {
    _style.textColor(std::move(color));
    invalidateRenderCache();
}

void TextView::setOutline(double outline) {
    _style.outline(outline);
    invalidateRenderCache();
}

void TextView::setOutlineColor(Color color) {
    _style.outlineColor(std::move(color));
    invalidateRenderCache();
}

void TextView::render(const RenderTarget* renderTarget, const Rectangle<int>& area) {
    if (!_font) { return; }

    auto distanceFieldShader = this->distanceFieldShader();
    distanceFieldShader->enableSupersampling(true);

    std::vector<shaders::DistanceFieldShader::Region> regions;
    const auto fillEdge = std::min(std::max(1.0 - _style.weight() / 200.0, 0.0), 1.0);
    if (_style.outline()) {
        regions.emplace_back(1.0, _style.outlineColor(), fillEdge - (1.0 - fillEdge) * _style.outline() / 100.0, _style.outlineColor());
    }
    regions.emplace_back(1.0, _style.textColor(), fillEdge, _style.textColor());
    distanceFieldShader->setRegions(std::move(regions));

    distanceFieldShader->setColor(Color::kWhite);
    _renderBitmapText(distanceFieldShader);

    distanceFieldShader->flush();
}

void TextView::layout() {
    _updateLines();
}

void TextView::windowChanged() {
    if (window() && !_style.fontTexture().empty() && !_style.fontMetadata().empty()) {
        _font = window()->loadBitmapFontResource(_style.fontTexture().c_str(), _style.fontMetadata().c_str());
        _updateLines();
    }
}

std::vector<std::basic_string<BitmapFont::GlyphId>> TextView::_computeLines(double width, double height) const {
    std::vector<std::basic_string<BitmapFont::GlyphId>> lines;
    if (!_font) { return lines; }

    auto fontScale = _style.textSize() / _font->size();

    std::basic_string<BitmapFont::GlyphId> line;

    static constexpr std::array<BitmapFont::GlyphId, 3> ellipses{{ 0x2e, 0x2e, 0x2e }};
    auto ellipsesWidth = _lineWidth({ellipses.data(), ellipses.size()}, fontScale);
    bool skipUntilNewLine = false;
    auto linesToShow = std::max<size_t>(1, floor(height / lineHeight()));
    auto useEllipses = _style.ellipsesEnabled() && _style.overflowBehavior() != Style::OverflowBehavior::kShrink;

    for (auto& glyph : _glyphs) {
        bool needsEllipses = false;

        if (glyph == '\n') {
            if (lines.size() + 1 >= linesToShow && useEllipses) {
                needsEllipses = true;
            } else {
                lines.emplace_back(std::move(line));
                line.clear();
                skipUntilNewLine = false;
                continue;
            }
        } else if (skipUntilNewLine) {
            continue;
        } else {
            line.push_back(glyph);

            if (_lineWidth(line, fontScale) > width) {
                if (_style.overflowBehavior() == Style::OverflowBehavior::kWrap && lines.size() + 1 < linesToShow) {
                    bool didBreak = false;
                    // try to break
                    for (size_t i = line.size(); i > 0; --i) {
                        auto& candidate = line[i - 1];
                        auto glyph = _font->glyph(candidate);
                        if (glyph && !glyph->width) {
                            // whitespace
                            if (i > 0) {
                                lines.emplace_back(&line[0], i - 1);
                            }
                            if (i < line.size()) {
                                line = line.substr(i);
                            } else {
                                line.clear();
                            }
                            didBreak = true;
                            break;
                        }
                    }
                    if (!didBreak && !line.empty()) {
                        // just put this character on a new line
                        auto last = line.back();
                        line.pop_back();
                        lines.emplace_back(std::move(line));
                        line.clear();
                        line += last;
                    }
                } else if (useEllipses) {
                    needsEllipses = true;
                }
            }
        }

        if (needsEllipses) {
            // rewind until the ellipses fit
            while (!line.empty() && _lineWidth({line.data(), line.size()}, fontScale) + ellipsesWidth > width) {
                line.pop_back();
            }
            // trim whitespace
            while (!line.empty()) {
                auto glyph = _font->glyph(line.back());
                if (!glyph || glyph->width) {
                    break;
                }
                line.pop_back();
            }
            // add the ellipses
            line.insert(line.end(), ellipses.begin(), ellipses.end());
            if (lines.size() >= linesToShow) {
                break;
            }
            skipUntilNewLine = true;
        }
    }

    if (!line.empty()) {
        lines.emplace_back(std::move(line));
    }

    return lines;
}

void TextView::_updateLines() {
    if (!_font) { return; }

    _lines = _computeLines(bounds().width, bounds().height);

    _textWidth = 0.0;
    auto fontScale = _style.textSize() / _font->size();
    for (auto& line : _lines) {
        _textWidth = std::max(_textWidth, _lineWidth({line.data(), line.size()}, fontScale));
    }

    invalidateRenderCache();
}

void TextView::_renderBitmapText(shaders::DistanceFieldShader* shader) {
    // TODO: this could be much more optimized (e.g. via display list or vbo)

    auto& texture = _font->texture();
    if (!texture->isLoaded()) { return; }

    auto fontScale = _fontScale();
    auto lineSpacing = _font->lineSpacing() * fontScale;
    auto y = _calcYOffset();

    for (auto& line : _lines) {
        double x = _calcXOffset(line);

        for (size_t i = 0; i < line.size(); ++i) {
            if (i > 0) {
                x += _font->kerning(line[i - 1], line[i]) * fontScale + _style.letterSpacing();
            }
            auto glyph = _font->glyph(line[i]);
            if (glyph) {
                Rectangle<double> glyphBounds(x + glyph->xOffset * fontScale, y + glyph->yOffset * fontScale, glyph->width * fontScale, glyph->height * fontScale);
                auto textureScale = glyphBounds.width / glyph->textureWidth;
                shader->setTexture(
                    *texture,
                    glyphBounds.x - glyph->textureX * textureScale,
                    glyphBounds.y - glyph->textureY * textureScale,
                    texture->width() * textureScale,
                    texture->height() * textureScale
                );
                shapes::Rectangle(glyphBounds.x, glyphBounds.y, glyphBounds.width, glyphBounds.height).draw(shader);
                x += glyph->xAdvance * fontScale;
            }
        }

        y += lineSpacing;
    }
}

double TextView::_calcXOffset(const std::basic_string<BitmapFont::GlyphId>& line) const {
    if (!_font) { return 0; }

    auto textWidth = _lineWidth(line, _fontScale());

    double x = 0.0;
    if (_style.horizontalAlignment() == Style::HorizontalAlignment::kCenter) {
        x = (bounds().width - textWidth) * 0.5;
    } else if (_style.horizontalAlignment() == Style::HorizontalAlignment::kRight) {
        x = bounds().width - textWidth;
    }

    return x;
}

double TextView::_calcYOffset() const {
    if (!_font) { return 0; }

    auto y = 0.0;
    if (_style.verticalAlignment() == Style::VerticalAlignment::kMiddle) {
        y = (bounds().height - textHeight()) * 0.5;
    } else if (_style.verticalAlignment() == Style::VerticalAlignment::kBottom) {
        y = bounds().height - textHeight();
    }

    return y;
}

double TextView::_fontScale() const {
    if (_style.overflowBehavior() == Style::OverflowBehavior::kShrink && _textWidth > bounds().width) {
        return _style.textSize() / _font->size() * bounds().width / _textWidth;
    }
    return _style.textSize() / _font->size();
}

double TextView::_lineWidth(stdts::basic_string_view<BitmapFont::GlyphId> line, double fontScale) const {
    auto letterSpacing = line.empty() ? 0 : (line.size()-1) * _style.letterSpacing();
    return _font->width(line.data(), line.size()) * fontScale + letterSpacing;
}

} // namespace okui::views
<|endoftext|>


<commit_before>#include "visuinstrument.h"

#include <QPainter>
#include <QStyleOption>

/**
 * @brief Instrument::signalUpdated
 * Method used by signal to notify instrument of change.
 * @param signal
 */
void VisuInstrument::signalUpdated(const VisuSignal* const signal)
{
    this->mSignal = signal;
    render();
}

void VisuInstrument::initialUpdate(const VisuSignal* const signal)
{
    this->mSignal = signal;
    mFirstRun = true;
    render();
}

quint16 VisuInstrument::getId()
{
    return cId;
}

quint16 VisuInstrument::getSignalId()
{
    return cSignalId;
}

void VisuInstrument::render()
{
    clearPixmaps();

    if (mFirstRun)
    {
        QPainter painter_static(&mPixmapStatic);
        painter_static.setRenderHint(QPainter::Antialiasing);
        renderStatic(&painter_static);
        mFirstRun = false;
    }

    QPainter painter_dynamic(&mPixmap);
    painter_dynamic.setRenderHint(QPainter::Antialiasing);
    painter_dynamic.drawPixmap(0, 0, mPixmapStatic);
    renderDynamic(&painter_dynamic);

    update();
}

void VisuInstrument::paintEvent(QPaintEvent* event)
{
    (void)event;    // supress compiler warning about unused parameter
    QPainter painter(this);
#if 0
    // allow for custom stylesheets
    QStyleOption option;
    option.initFrom(this);
    style()->drawPrimitive(QStyle::PE_Widget, &option, &painter, this);
#endif
    // draw instrument
    painter.drawPixmap(0, 0, mPixmap);

    if (mActive)
    {
        setPen(&painter, cColorStatic, 2);
        painter.drawRect(0, 0, cWidth, cHeight);
    }
}

void VisuInstrument::setFont(QPainter* painter, int fontSize)
{
    QFont font;
    font.setPointSize(fontSize);
    painter->setFont(font);
}

void VisuInstrument::setPen(QPainter* painter, QColor color, int thickness)
{
    QPen pen;
    pen.setColor(color);
    pen.setWidth(thickness);
    painter->setPen(pen);
}

void VisuInstrument::setBrush(QPainter* painter, QColor color)
{
    painter->setBrush(color);
}

void VisuInstrument::clear(QPainter* painter)
{
    painter->fillRect(0, 0, cWidth, cHeight, cColorBackground);
}
<commit_msg>Fixed instrument static pixmap dissapearing<commit_after>#include "visuinstrument.h"

#include <QPainter>
#include <QStyleOption>

/**
 * @brief Instrument::signalUpdated
 * Method used by signal to notify instrument of change.
 * @param signal
 */
void VisuInstrument::signalUpdated(const VisuSignal* const signal)
{
    this->mSignal = signal;
    render();
}

void VisuInstrument::initialUpdate(const VisuSignal* const signal)
{
    this->mSignal = signal;
    mFirstRun = true;
    render();
}

quint16 VisuInstrument::getId()
{
    return cId;
}

quint16 VisuInstrument::getSignalId()
{
    return cSignalId;
}

void VisuInstrument::render()
{
    mPixmap.fill(Qt::transparent);

    if (mFirstRun)
    {
        mPixmapStatic.fill(Qt::transparent);
        QPainter painter_static(&mPixmapStatic);
        painter_static.setRenderHint(QPainter::Antialiasing);
        renderStatic(&painter_static);
        mFirstRun = false;
    }

    QPainter painter_dynamic(&mPixmap);
    painter_dynamic.setRenderHint(QPainter::Antialiasing);
    painter_dynamic.drawPixmap(0, 0, mPixmapStatic);
    renderDynamic(&painter_dynamic);

    update();
}

void VisuInstrument::paintEvent(QPaintEvent* event)
{
    (void)event;    // supress compiler warning about unused parameter
    QPainter painter(this);
#if 0
    // allow for custom stylesheets
    QStyleOption option;
    option.initFrom(this);
    style()->drawPrimitive(QStyle::PE_Widget, &option, &painter, this);
#endif
    // draw instrument
    painter.drawPixmap(0, 0, mPixmap);

    if (mActive)
    {
        setPen(&painter, cColorStatic, 2);
        painter.drawRect(0, 0, cWidth, cHeight);
    }
}

void VisuInstrument::setFont(QPainter* painter, int fontSize)
{
    QFont font;
    font.setPointSize(fontSize);
    painter->setFont(font);
}

void VisuInstrument::setPen(QPainter* painter, QColor color, int thickness)
{
    QPen pen;
    pen.setColor(color);
    pen.setWidth(thickness);
    painter->setPen(pen);
}

void VisuInstrument::setBrush(QPainter* painter, QColor color)
{
    painter->setBrush(color);
}

void VisuInstrument::clear(QPainter* painter)
{
    painter->fillRect(0, 0, cWidth, cHeight, cColorBackground);
}
<|endoftext|>


<commit_before>/*
 *  Copyright 2010-2012 Anders Wallin (anders.e.e.wallin "at" gmail.com)
 *
 *  This file is part of OpenVoronoi.
 *
 *  OpenVoronoi 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.
 *
 *  OpenVoronoi 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 OpenVoronoi.  If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once

#include <queue>
#include <set>
#include <boost/tuple/tuple.hpp>

#include "common/point.hpp"
#include "graph.hpp"
#include "vertex_positioner.hpp"
#include "filter.hpp"
#include "kdtree.hpp"

/*! \mainpage OpenVoronoi
 *
 * \section intro_sec Introduction
 *
 * OpenVoronoi is a c++ library with python bindings for calculating 2D voronoi-diagrams of point,
 * line-segment, and circular-arc(not implement yet!) sites.
 * An incremental topology-oriented algorithm is used.
 *
 * See github for build/install instructions https://github.com/aewallin/openvoronoi
 *
 * \section Notes on Test Coverage
 * - compile using CMAKE_BUILD_TYPE=Coverage  (uses "-fprofile-arcs -ftest-coverage" )
 * - There's a custom target "make coverage-report" that runs the following:
 *  - reset counters "lcov --directory ./ --zerocounters"
 *  - run tests with "ctest -D Experimental -R cpptest_minimal"
 *  - generate an info-file with "lcov --directory ./ --capture --output-file app.info"
 *  - generate html output with "genhtml --output-directory coverage --title OpenVoronoi Test Coverage app.info"
 * - point your browser to build/doc/index.html to see the output
 */

namespace ovd
{
/*!
 * \namespace ovd
 * \brief OpenVoronoi classes and functions
 */

class VoronoiDiagramChecker;

/// \brief KD-tree for 2D point location
///
/// a kd-tree is used for nearest-neighbor search when inserting point sites
struct kd_point {
    /// default ctor
    kd_point() : p(0,0), face(0) { }
    /// kd-point with given position and HEFace
    kd_point(Point pt, HEFace f) : p(pt), face(f) { }
    /// kd-point at given position
    kd_point(Point pt) : p(pt), face(0) { }
    /// distance (suared) to given point
    double dist(const kd_point& pt) const {
        return (p.x-pt.p.x)*(p.x-pt.p.x) + (p.y-pt.p.y)*(p.y-pt.p.y);
    }
    /// return x or y coordinate of Point
    double operator[](unsigned int i) const {
        return i == 0 ? p.x : p.y;
    }
    /// return x or y coordinate of Point
    double& operator[](unsigned int i) {
        return i == 0 ? p.x : p.y;
    }
    Point p; ///< position of 2D PointSite
    HEFace face; ///< the HEFace correspoinding to the PointSite
};

/// type of the KD-tree used for nearest-neighbor search
typedef kdtree::KDTree<kd_point> kd_type;

/// \brief Voronoi diagram.
///
/// see http://en.wikipedia.org/wiki/Voronoi_diagram
///
/// the dual of a voronoi diagram is the delaunay diagram(triangulation).
///  voronoi-faces are dual to delaunay-vertices.
///  vornoi-vertices are dual to delaunay-faces
///  voronoi-edges are dual to delaunay-edges
class VoronoiDiagram {
public:
    VoronoiDiagram(double far, unsigned int n_bins);
    virtual ~VoronoiDiagram();
    int insert_point_site(const Point& p, int step=0);
    bool insert_line_site(int idx1, int idx2, int step=99); // default step should make algorithm run until the end!
    void insert_arc_site(int idx1, int idx2, const Point& c, bool cw, int step=99);

    /// return the far radius
    double get_far_radius() const {return far_radius;}
    /// return number of point sites in diagram
    int num_point_sites() const {return num_psites-3;} // the three initial vertices don't count
    /// return number of line-segments sites in diagram
    int num_line_sites() const {return num_lsites;}
    /// return number of voronoi-vertices
    int num_vertices() const { return g.num_vertices()-num_point_sites(); }
    /// return number of faces in graph
    int num_faces() const { return g.num_faces(); }
    int num_split_vertices() const;
    /// return reference to graph \todo not elegant. only used by vd2svg ?
    HEGraph& get_graph_reference() {return g;}

    std::string print() const;
    /// reset vertex index count \todo not very elegant...
    static void reset_vertex_count() { VoronoiVertex::reset_count(); }
    /// turn on debug output
    void debug_on() {debug=true;}
    /// set silent mode on/off
    void set_silent(bool b) {
        silent=b;
        vpos->set_silent(silent);
    }
    bool check();
    void filter( Filter* flt);
    void filter_reset();
protected:
    /// type for item in VertexQueue. pair of vertex-desxriptor and in_circle predicate
    typedef std::pair<HEVertex, double> VertexDetPair;
    /// \brief comparison-predicate for VertexQueue
    ///
    /// in augment_vertex_set() we grow the delete-tree by processing vertices
    /// one-by-one from a priority_queue. This is the priority_queue sort predicate.
    /// We handle vertices with a large fabs( in_circle() ) first, since we
    /// believe their predicate to be more reliable.
    class abs_comparison {
    public:
      /// return true if absolute-value of lhs.second is smaller than rhs.second
      bool operator() (const VertexDetPair& lhs, const VertexDetPair&rhs) const {
        return ( fabs(lhs.second) < fabs(rhs.second) );
      }
    };

    /// priority_queue for vertex for processing
    // sorted by decreasing fabs() of in_circle-predicate, so that the vertices whose IN/OUT status we are 'most certain' about are processed first
    typedef std::priority_queue< VertexDetPair , std::vector<VertexDetPair>, abs_comparison > VertexQueue;

    /// \brief data required for adding a new edge
    ///
    /// used in add_edge() for storing information related to
    /// the new edge.
    struct EdgeData {
        HEEdge v1_prv; ///< edge prior to v1
        HEVertex v1;   ///< NEW edge source
        HEEdge v1_nxt; ///< edge following v1
        HEEdge v2_prv; ///< edge prior to v2
        HEVertex v2;   ///< NEW edge target
        HEEdge v2_nxt; ///< edge following v2
        HEFace f;      ///< face of v1 and v2
    };

    void initialize();
    HEVertex   find_seed_vertex(HEFace f, Site* site);
    EdgeVector find_in_out_edges();
    EdgeData   find_edge_data(HEFace f, VertexVector startverts, std::pair<HEVertex,HEVertex> segment);
    EdgeVector find_split_edges(HEFace f, Point pt1, Point pt2);
    bool       find_split_vertex(HEFace f, HEVertex& v);
    std::pair<HEVertex,HEVertex> find_endpoints(int idx1, int idx2);
    bool null_vertex_target( HEVertex v , HEVertex& trg);
    void augment_vertex_set( Site* site);
    bool predicate_c4(HEVertex v);
    bool predicate_c5(HEVertex v);
    void mark_adjacent_faces(HEVertex v, Site* site);
    void mark_adjacent_faces_p( HEVertex v );
    void mark_vertex(HEVertex& v,  Site* site);
    void   add_vertices( Site* site );
    HEFace add_face(Site* site);
    void   add_edges(HEFace new_f1, HEFace f);
    void   add_edges(HEFace new_f1, HEFace f, HEFace new_f2, std::pair<HEVertex,HEVertex> seg);
    void   add_edge(EdgeData ed, HEFace new1, HEFace new2=0);
    void   add_separator(HEFace f, HEFace nf, boost::tuple<HEEdge, HEVertex, HEEdge,bool> target, HEVertex endp, Site* s1, Site* s2);
    void   add_split_vertex(HEFace f, Site* s);
    boost::tuple<HEVertex,HEFace,HEVertex,HEVertex,HEFace> find_null_face(HEVertex start, HEVertex other, Point l, Point dir, Site* new_site);
    boost::tuple<HEEdge,HEVertex,HEEdge,bool> find_separator_target(HEFace f, HEVertex endp);
    std::pair<HEVertex,HEFace> process_null_edge(Point dir, HEEdge next_edge , bool k3, bool next_prev);
    HEVertex add_separator_vertex(HEVertex endp, HEEdge edge, Point sep_dir);
    void repair_face( HEFace f );
    void repair_face( HEFace f , std::pair<HEVertex,HEVertex> segs,
                                 std::pair<HEFace,HEFace> nulled_faces,
                                 std::pair<HEFace,HEFace> null_faces );
    void remove_vertex_set();
    void remove_split_vertex(HEFace f);
    void reset_status();
    int num_new_vertices(HEFace f);
// HELPER-CLASSES
    VoronoiDiagramChecker* vd_checker; ///< sanity-checks on the diagram are done by this helper class
    kd_type* kd_tree; ///< kd-tree for nearest neighbor search during point Site insertion
    VertexPositioner* vpos; ///< an algorithm for positioning vertices
// DATA
    typedef std::map<int,HEVertex> VertexMap; ///< type for vertex-index to vertex-descriptor map
    typedef std::pair<int,HEVertex> VertexMapPair; ///< associate vertex index with vertex descriptor

    VertexMap vertex_map; ///< map from int handles to vertex-descriptors, used in insert_line_site()
    VertexQueue vertexQueue; ///< queue of vertices to be processed
    HEGraph g; ///< the half-edge diagram of the vd
    double far_radius; ///< sites must fall within a circle with radius far_radius
    int num_psites; ///< the number of point sites
    int num_lsites; ///< the number of line-segment sites
    int num_arc_sites; ///< the number of arc-sites
    FaceVector incident_faces; ///< temporary variable for ::INCIDENT faces, will be reset to ::NONINCIDENT after a site has been inserted
    std::set<HEVertex> modified_vertices; ///< temporary variable for in-vertices, out-vertices that need to be reset after a site has been inserted
    VertexVector v0; ///< IN-vertices, i.e. to-be-deleted
    bool debug; ///< turn debug output on/off
    bool silent; ///< no warnings emitted when silent==true
private:
    VoronoiDiagram(); // don't use.
};

/// \brief error-functor to locate ::SPLIT vertices
///
/// for passing to numerical boost::toms748 root-finding algorithm
class SplitPointError {
public:
    /// \param gi graph
    /// \param split_edge the edge on which we want to position a SPLIT vertex
    /// \param pt1 first point of split-line
    /// \param pt2 second point of split-line
    SplitPointError(HEGraph& gi, HEEdge split_edge, Point pt1, Point pt2) :
    g(gi),  edge(split_edge), p1(pt1), p2(pt2)
    {}

    /// \return signed distance to the pt1-pt2 line from edge-point at given offset \a t
    double operator()(const double t) {
        Point p = g[edge].point(t);
        // line: pt1 + u*(pt2-pt1) = p
        //   (p-pt1) dot (pt2-pt1) = u* (pt2-pt1) dot (pt2-pt1)

        double u = (p-p1).dot(p2-p1) / ( (p2-p1).dot(p2-p1) );
        Point proj = p1 + u*(p2-p1);
        double dist = (proj-p).norm();
        double sign;
        if ( p.is_right(p1,p2) )
            sign = +1;
        else
            sign = -1;

        return sign*dist;
    }
private:
    HEGraph& g;      ///< reference to vd-graph
    HEEdge edge;     ///< the HEEdge on which we position the new SPLIT vertex
    Point p1;     ///< first point of the split-line
    Point p2;    ///< second point of the split-line
};

} // end ovd namespace

// end voronoidiagram.hpp
<commit_msg>add some doxygen doc<commit_after>/*
 *  Copyright 2010-2012 Anders Wallin (anders.e.e.wallin "at" gmail.com)
 *
 *  This file is part of OpenVoronoi.
 *
 *  OpenVoronoi 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.
 *
 *  OpenVoronoi 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 OpenVoronoi.  If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once

#include <queue>
#include <set>
#include <boost/tuple/tuple.hpp>

#include "common/point.hpp"
#include "graph.hpp"
#include "vertex_positioner.hpp"
#include "filter.hpp"
#include "kdtree.hpp"

/*! \mainpage OpenVoronoi
 *
 * \author Anders E. Wallin (anders.e.e.wallin "at" gmail.com)
 * \section intro_sec Introduction
 *
 * OpenVoronoi is a c++ library with python bindings (using boost::python) for calculating 2D voronoi-diagrams of point,
 * line-segment, and circular-arc(not implement yet!) sites.
 * An incremental topology-oriented algorithm is used.
 *
 * See github for build/install instructions https://github.com/aewallin/openvoronoi
 *
 * DEB packages for Ubuntu are available at https://launchpad.net/~anders-e-e-wallin/+archive/cam
 *
 * Output gallery: https://picasaweb.google.com/106188605401091280402/OpenVoronoiExamples
 *
 * \section Utilities
 * - Offset
 * - Medial-Axis
 * - SVG output
 *
 * \section Tests
 * Tests are written for CTest.
 * - "ctest -R cpp" runs only the c++ tests (these are fast)
 * - "ctest" runs all tests (some may be slow)
 * Some tests use truetype-tracer (font geometry source), and some use CGAL_RPG (random polygon generator).
 * \section coverage Code Coverage Testing
 * - compile using CMAKE_BUILD_TYPE=Coverage  (uses "-fprofile-arcs -ftest-coverage" )
 * - There is a custom target "make coverage-report" that runs the following:
 *  - reset counters "lcov --directory ./ --zerocounters"
 *  - run tests with "ctest -D Experimental -R cpptest_minimal"
 *  - generate an info-file with "lcov --directory ./ --capture --output-file app.info"
 *  - generate html output with "genhtml --output-directory coverage --title OpenVoronoi Test Coverage app.info"
 * - point your browser to build/doc/index.html to see the output
 */

namespace ovd
{
/*!
 * \namespace ovd
 * \brief OpenVoronoi classes and functions
 */

class VoronoiDiagramChecker;

/// \brief KD-tree for 2D point location
///
/// a kd-tree is used for nearest-neighbor search when inserting point sites
struct kd_point {
    /// default ctor
    kd_point() : p(0,0), face(0) { }
    /// kd-point with given position and HEFace
    kd_point(Point pt, HEFace f) : p(pt), face(f) { }
    /// kd-point at given position
    kd_point(Point pt) : p(pt), face(0) { }
    /// distance (suared) to given point
    double dist(const kd_point& pt) const {
        return (p.x-pt.p.x)*(p.x-pt.p.x) + (p.y-pt.p.y)*(p.y-pt.p.y);
    }
    /// return x or y coordinate of Point
    double operator[](unsigned int i) const {
        return i == 0 ? p.x : p.y;
    }
    /// return x or y coordinate of Point
    double& operator[](unsigned int i) {
        return i == 0 ? p.x : p.y;
    }
    Point p; ///< position of 2D PointSite
    HEFace face; ///< the HEFace correspoinding to the PointSite
};

/// type of the KD-tree used for nearest-neighbor search
typedef kdtree::KDTree<kd_point> kd_type;

/// \brief Voronoi diagram.
///
/// see http://en.wikipedia.org/wiki/Voronoi_diagram
///
/// the dual of a voronoi diagram is the delaunay diagram(triangulation).
///  voronoi-faces are dual to delaunay-vertices.
///  vornoi-vertices are dual to delaunay-faces
///  voronoi-edges are dual to delaunay-edges
class VoronoiDiagram {
public:
    VoronoiDiagram(double far, unsigned int n_bins);
    virtual ~VoronoiDiagram();
    int insert_point_site(const Point& p, int step=0);
    bool insert_line_site(int idx1, int idx2, int step=99); // default step should make algorithm run until the end!
    void insert_arc_site(int idx1, int idx2, const Point& c, bool cw, int step=99);

    /// return the far radius
    double get_far_radius() const {return far_radius;}
    /// return number of point sites in diagram
    int num_point_sites() const {return num_psites-3;} // the three initial vertices don't count
    /// return number of line-segments sites in diagram
    int num_line_sites() const {return num_lsites;}
    /// return number of voronoi-vertices
    int num_vertices() const { return g.num_vertices()-num_point_sites(); }
    /// return number of faces in graph
    int num_faces() const { return g.num_faces(); }
    int num_split_vertices() const;
    /// return reference to graph \todo not elegant. only used by vd2svg ?
    HEGraph& get_graph_reference() {return g;}

    std::string print() const;
    /// reset vertex index count \todo not very elegant...
    static void reset_vertex_count() { VoronoiVertex::reset_count(); }
    /// turn on debug output
    void debug_on() {debug=true;}
    /// set silent mode on/off
    void set_silent(bool b) {
        silent=b;
        vpos->set_silent(silent);
    }
    bool check();
    void filter( Filter* flt);
    void filter_reset();
protected:
    /// type for item in VertexQueue. pair of vertex-desxriptor and in_circle predicate
    typedef std::pair<HEVertex, double> VertexDetPair;
    /// \brief comparison-predicate for VertexQueue
    ///
    /// in augment_vertex_set() we grow the delete-tree by processing vertices
    /// one-by-one from a priority_queue. This is the priority_queue sort predicate.
    /// We handle vertices with a large fabs( in_circle() ) first, since we
    /// believe their predicate to be more reliable.
    class abs_comparison {
    public:
      /// return true if absolute-value of lhs.second is smaller than rhs.second
      bool operator() (const VertexDetPair& lhs, const VertexDetPair&rhs) const {
        return ( fabs(lhs.second) < fabs(rhs.second) );
      }
    };

    /// priority_queue for vertex for processing
    // sorted by decreasing fabs() of in_circle-predicate, so that the vertices whose IN/OUT status we are 'most certain' about are processed first
    typedef std::priority_queue< VertexDetPair , std::vector<VertexDetPair>, abs_comparison > VertexQueue;

    /// \brief data required for adding a new edge
    ///
    /// used in add_edge() for storing information related to
    /// the new edge.
    struct EdgeData {
        HEEdge v1_prv; ///< edge prior to v1
        HEVertex v1;   ///< NEW edge source
        HEEdge v1_nxt; ///< edge following v1
        HEEdge v2_prv; ///< edge prior to v2
        HEVertex v2;   ///< NEW edge target
        HEEdge v2_nxt; ///< edge following v2
        HEFace f;      ///< face of v1 and v2
    };

    void initialize();
    HEVertex   find_seed_vertex(HEFace f, Site* site);
    EdgeVector find_in_out_edges();
    EdgeData   find_edge_data(HEFace f, VertexVector startverts, std::pair<HEVertex,HEVertex> segment);
    EdgeVector find_split_edges(HEFace f, Point pt1, Point pt2);
    bool       find_split_vertex(HEFace f, HEVertex& v);
    std::pair<HEVertex,HEVertex> find_endpoints(int idx1, int idx2);
    bool null_vertex_target( HEVertex v , HEVertex& trg);
    void augment_vertex_set( Site* site);
    bool predicate_c4(HEVertex v);
    bool predicate_c5(HEVertex v);
    void mark_adjacent_faces(HEVertex v, Site* site);
    void mark_adjacent_faces_p( HEVertex v );
    void mark_vertex(HEVertex& v,  Site* site);
    void   add_vertices( Site* site );
    HEFace add_face(Site* site);
    void   add_edges(HEFace new_f1, HEFace f);
    void   add_edges(HEFace new_f1, HEFace f, HEFace new_f2, std::pair<HEVertex,HEVertex> seg);
    void   add_edge(EdgeData ed, HEFace new1, HEFace new2=0);
    void   add_separator(HEFace f, HEFace nf, boost::tuple<HEEdge, HEVertex, HEEdge,bool> target, HEVertex endp, Site* s1, Site* s2);
    void   add_split_vertex(HEFace f, Site* s);
    boost::tuple<HEVertex,HEFace,HEVertex,HEVertex,HEFace> find_null_face(HEVertex start, HEVertex other, Point l, Point dir, Site* new_site);
    boost::tuple<HEEdge,HEVertex,HEEdge,bool> find_separator_target(HEFace f, HEVertex endp);
    std::pair<HEVertex,HEFace> process_null_edge(Point dir, HEEdge next_edge , bool k3, bool next_prev);
    HEVertex add_separator_vertex(HEVertex endp, HEEdge edge, Point sep_dir);
    void repair_face( HEFace f );
    void repair_face( HEFace f , std::pair<HEVertex,HEVertex> segs,
                                 std::pair<HEFace,HEFace> nulled_faces,
                                 std::pair<HEFace,HEFace> null_faces );
    void remove_vertex_set();
    void remove_split_vertex(HEFace f);
    void reset_status();
    int num_new_vertices(HEFace f);
// HELPER-CLASSES
    VoronoiDiagramChecker* vd_checker; ///< sanity-checks on the diagram are done by this helper class
    kd_type* kd_tree; ///< kd-tree for nearest neighbor search during point Site insertion
    VertexPositioner* vpos; ///< an algorithm for positioning vertices
// DATA
    typedef std::map<int,HEVertex> VertexMap; ///< type for vertex-index to vertex-descriptor map
    typedef std::pair<int,HEVertex> VertexMapPair; ///< associate vertex index with vertex descriptor

    VertexMap vertex_map; ///< map from int handles to vertex-descriptors, used in insert_line_site()
    VertexQueue vertexQueue; ///< queue of vertices to be processed
    HEGraph g; ///< the half-edge diagram of the vd
    double far_radius; ///< sites must fall within a circle with radius far_radius
    int num_psites; ///< the number of point sites
    int num_lsites; ///< the number of line-segment sites
    int num_arc_sites; ///< the number of arc-sites
    FaceVector incident_faces; ///< temporary variable for ::INCIDENT faces, will be reset to ::NONINCIDENT after a site has been inserted
    std::set<HEVertex> modified_vertices; ///< temporary variable for in-vertices, out-vertices that need to be reset after a site has been inserted
    VertexVector v0; ///< IN-vertices, i.e. to-be-deleted
    bool debug; ///< turn debug output on/off
    bool silent; ///< no warnings emitted when silent==true
private:
    VoronoiDiagram(); // don't use.
};

/// \brief error-functor to locate ::SPLIT vertices
///
/// for passing to numerical boost::toms748 root-finding algorithm
class SplitPointError {
public:
    /// \param gi graph
    /// \param split_edge the edge on which we want to position a SPLIT vertex
    /// \param pt1 first point of split-line
    /// \param pt2 second point of split-line
    SplitPointError(HEGraph& gi, HEEdge split_edge, Point pt1, Point pt2) :
    g(gi),  edge(split_edge), p1(pt1), p2(pt2)
    {}

    /// \return signed distance to the pt1-pt2 line from edge-point at given offset \a t
    double operator()(const double t) {
        Point p = g[edge].point(t);
        // line: pt1 + u*(pt2-pt1) = p
        //   (p-pt1) dot (pt2-pt1) = u* (pt2-pt1) dot (pt2-pt1)

        double u = (p-p1).dot(p2-p1) / ( (p2-p1).dot(p2-p1) );
        Point proj = p1 + u*(p2-p1);
        double dist = (proj-p).norm();
        double sign;
        if ( p.is_right(p1,p2) )
            sign = +1;
        else
            sign = -1;

        return sign*dist;
    }
private:
    HEGraph& g;      ///< reference to vd-graph
    HEEdge edge;     ///< the HEEdge on which we position the new SPLIT vertex
    Point p1;     ///< first point of the split-line
    Point p2;    ///< second point of the split-line
};

} // end ovd namespace

// end voronoidiagram.hpp
<|endoftext|>