<commit_before>#ifndef GUARD_database_connection_hpp
#define GUARD_database_connection_hpp

#include "sqloxx_exceptions.hpp"
#include <sqlite3.h>
#include <boost/cstdint.hpp>
#include <boost/noncopyable.hpp>
#include <cassert>
#include <string>
#include <vector>

/**
 *
 * @todo Move sqloxx code to a separate library.
 *
 * @todo Make the DatabaseConnection class provide for a means
 * to check whether the particular file being connected to is
 * the right kind of file for the application that is using
 * the DatabaseConnection class. At the moment it just checks whether
 * the file exists. This could involve overriding some method or other.
 *
 * @todo Use boost::filesystem::path to make filepath passed to
 * activate_database more portable.
 *
 * @todo Consider supplying public member function to close any
 * database connections and shut down SQLite3. Current this is done
 * in the destructor, but this can't throw.
 *
 * @todo DatabaseConnection::is_valid should probably do more than
 * just check whether m_connection exists. It should probably also
 * at least check SQLite error status.
 *
 * @todo I am often calling sqlite3_finalize() before throwing
 * SQLiteException(). Is this necessary? Is this desirable? What
 * about if reset() is preferred by the caller?
 */

namespace sqloxx
{

/**
 * Class to manage connection to SQLite3 database and execute
 * SQL on that database.
 *
 * This class can be derived from and certain member functions
 * overridden to create application-specific database code.
 */
class DatabaseConnection:
	private boost::noncopyable
{
public:

	/**
	 * Initializes SQLite3 and creates a database connection
	 * initially set to null.
	 *
	 * @throws SQLiteException if SQLite3 initialization fails,
	 * or if database connection cannot be opened.
	 */
	DatabaseConnection();

	/**
	 * Closes any open SQLite3 database connection, and also
	 * shuts down SQLite3.
	 *
	 * Does not throw. If SQLite3 connection closure or shutdown fails,
	 * the application is aborted with a diagnostic message written to
	 * std::clog.
	 */
	virtual ~DatabaseConnection();

	/**
	 * Returns \c true iff the DatabaseConnection is connected to a
	 * database.
	 */
	virtual bool is_valid();

	/**
	 * Points the database connection to a specific file
	 * given by \c filename. If the file
	 * does not already exist it is created. After creation, a separate
	 * \c setup function is called, to set up
	 * any application specific tables or etc. and initial data as desired.
	 *
	 * To customise this "setup" in a derived class, override
	 * \c DatabaseConnection::setup (which does nothing by default)
	 * in your class derived from DatabaseConnection.
	 *
	 * \c DatabaseConnection::setup has the signature:\n
	 * 	<tt> virtual void setup() </tt>\n
	 * and is private. It should throw SQLiteException in case it fails.
	 *
	 * @param filename file to connect to
	 *
	 * @throws SQLiteException if SQLite3
	 * if database connection cannot be opened to the specified file,
	 * or if a new file is created but \c setup does not
	 * succeed.
	 */
	void open(char const* filename);

protected:

	/**
	 * Wrapper class for sqlite_stmt*.
	 *
	 * @todo The constructor to create a SQLStatement should reject strings
	 * containing semicolons, since compound statements are not handled by
	 * step() properly. There should be some other class SQLMultiStatement or
	 * something, which can then executed using a wrapper around sqlite3_exec.
	 */
	class SQLStatement;

	/**
	 * Throws a SQLiteException with the current sqlite3_errmsg passed
	 * to the constructor of the exception.
	 *
	 * This is essentially to save typing.
	 *
	 * @throws SQLiteException whenever called
	 */
	void throw_sqlite_exception();

	/**
	 * Executes a string on the database connection.
	 * This should be used only where the developer has complete
	 * control of the string being passed, to prevent SQL injection
	 * attacks. Generally, the functions provided by SQLStatement should
	 * be the preferred means for building and executing SQL statements.
	 */
	void execute_sql(std::string const& str);

	/**
	 * To find primary key of a table.
	 *
	 * Note, this function should not be used where \c table_name is
	 * an untrusted string.
	 *
	 * @returns a vector of the names of all the columns making up the
	 * primary key of the table named \c table_name. An empty vector
	 * is returned if there is no primary key.
	 *
	 * @throws SQLiteException in case of invalid table name or other
	 * SQL execution error.
	 *
	 * @param table_name name of table
	 */
	std::vector<std::string> primary_key(std::string const& table_name);

private:

	/**
	 * A connection to a SQLite3 database file.
	 *
	 * (Note this is a raw pointer not a smart pointer
	 * to facilitate more straightforward interaction with the SQLite
	 * C API.)
	 */
	sqlite3* m_connection;

	/**
	 * Create application-specific tables or perform other setup
	 * when database is opened.
	 *
	 * This function should be redefined in derived class if required
	 * to perform application-specific database setup.
	 *
	 * @throws sqloxx::SQLiteException if operation
	 * fails.
	 */
	virtual void setup();

};

class DatabaseConnection::SQLStatement:
	private boost::noncopyable
{
public:

	SQLStatement(DatabaseConnection& dbconn, std::string const& str);

	~SQLStatement();

	// Wrapper around SQLite bind functions
	void bind(std::string const& parameter_name, double value);
	void bind(std::string const& parameter_name, int value);
	void bind(std::string const& parameter_name, boost::int64_t value);
	void bind(std::string const& parameter_name, std::string const& str);

	/**
	 * Where a SQLStatement has a result set available,
	 * this function (template) can be used to extract the value at
	 * the \c indexth column of the current row (where \c index starts
	 * counting at 0).
	 *
	 * Currently the following types for T are supported:\n
	 *	\c boost::int64_t\n
	 *	int\n
	 *	double\n
	 *	std::string\n
	 *
	 * @param index is the column number (starting at 0) from which to
	 * read the value.
	 *
	 * @throws SQLiteException if:\n
	 * 	the index is out of range; or\n
	 * 	the requested column contains type that is incompatible with T.
	 */
	template <typename T>
	T extract(int index);

	// Wraps sqlite3_step
	// Returns true as long as there are further steps to go.
	bool step();

	// For executing statements which are not expected to return a result
	// set. SQLiteException is thrown if there are one or more result rows.
	void quick_step();

private:
	sqlite3_stmt* m_statement;
	DatabaseConnection& m_database_connection;

	// Return index no. of named parameter in statement
	int parameter_index(std::string const& parameter_name) const;

	/**
	 * Checks whether a column is available for extraction at
	 * index \c index, of type \c value_type, and throws an
	 * exception if not.
	 *
	 * @param index Position of column (starts from zero) in result
	 * row.
	 *
	 * @param value_type Should be a SQLite value type code, i.e. one of:\n
	 * 	SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, SQLITE_NULL.
	 *
	 * @throws SQLiteException if:\n
	 * 	There are no results available for extraction;\n
	 * 	\c index is out of range; or\n
	 * 	\c the value at position \c index is not of value type \c value_type.
	 */
	void check_column(int index, int value_type);

	// Check code is SQLITE_OK and if not finalize statement and
	// throw SQLiteException.
	void check_ok(int err_code);

};

// FUNCTION TEMPLATE DEFINITIONS

template <>
inline
int
DatabaseConnection::SQLStatement::extract<int>(int index)
{
	check_column(index, SQLITE_INTEGER);
	return sqlite3_column_int(m_statement, index);
}

template <>
inline
boost::int64_t
DatabaseConnection::SQLStatement::extract<boost::int64_t>(int index)
{
	check_column(index, SQLITE_INTEGER);
	return sqlite3_column_int64(m_statement, index);
}

template <>
inline
double
DatabaseConnection::SQLStatement::extract<double>(int index)
{
	check_column(index, SQLITE_FLOAT);
	return sqlite3_column_double(m_statement, index);
}

template <>
inline
std::string
DatabaseConnection::SQLStatement::extract<std::string>(int index)
{
	check_column(index, SQLITE_TEXT);
	const unsigned char* begin = sqlite3_column_text(m_statement, index);
	const unsigned char* end = begin;
	while (*end != '\0') ++end;
	return std::string(begin, end);
}

}  // namespace sqloxx

#endif  // GUARD_database_connection_hpp
<commit_msg>Updated todos.<commit_after>#ifndef GUARD_database_connection_hpp
#define GUARD_database_connection_hpp

#include "sqloxx_exceptions.hpp"
#include <sqlite3.h>
#include <boost/cstdint.hpp>
#include <boost/noncopyable.hpp>
#include <cassert>
#include <string>
#include <vector>

/**
 *
 * @todo Move sqloxx code to a separate library.
 *
 * @todo Make the DatabaseConnection class provide for a means
 * to check whether the particular file being connected to is
 * the right kind of file for the application that is using
 * the DatabaseConnection class. At the moment it just checks whether
 * the file exists. This could involve overriding some method or other.
 *
 * @todo Use boost::filesystem::path to make filepath passed to
 * activate_database more portable.
 *
 * @todo Consider supplying public member function to close any
 * database connections and shut down SQLite3. Current this is done
 * in the destructor, but this can't throw.
 *
 * @todo DatabaseConnection::is_valid should probably do more than
 * just check whether m_connection exists. It should probably also
 * at least check SQLite error status.
 *
 * @todo I am often calling sqlite3_finalize() before throwing
 * SQLiteException(). Is this necessary? Is this desirable? What
 * about if reset() is preferred by the caller?
 */

namespace sqloxx
{

/**
 * Class to manage connection to SQLite3 database and execute
 * SQL on that database.
 *
 * This class can be derived from and certain member functions
 * overridden to create application-specific database code.
 */
class DatabaseConnection:
	private boost::noncopyable
{
public:

	/**
	 * Initializes SQLite3 and creates a database connection
	 * initially set to null.
	 *
	 * @throws SQLiteException if SQLite3 initialization fails,
	 * or if database connection cannot be opened.
	 */
	DatabaseConnection();

	/**
	 * Closes any open SQLite3 database connection, and also
	 * shuts down SQLite3.
	 *
	 * Does not throw. If SQLite3 connection closure or shutdown fails,
	 * the application is aborted with a diagnostic message written to
	 * std::clog.
	 */
	virtual ~DatabaseConnection();

	/**
	 * Returns \c true iff the DatabaseConnection is connected to a
	 * database.
	 */
	virtual bool is_valid();

	/**
	 * Points the database connection to a specific file
	 * given by \c filename. If the file
	 * does not already exist it is created. After creation, a separate
	 * \c setup function is called, to set up
	 * any application specific tables or etc. and initial data as desired.
	 *
	 * To customise this "setup" in a derived class, override
	 * \c DatabaseConnection::setup (which does nothing by default)
	 * in your class derived from DatabaseConnection.
	 *
	 * \c DatabaseConnection::setup has the signature:\n
	 * 	<tt> virtual void setup() </tt>\n
	 * and is private. It should throw SQLiteException in case it fails.
	 *
	 * @param filename file to connect to
	 *
	 * @throws SQLiteException if SQLite3
	 * if database connection cannot be opened to the specified file,
	 * or if a new file is created but \c setup does not
	 * succeed.
	 */
	void open(char const* filename);

protected:

	/**
	 * Wrapper class for sqlite_stmt*.
	 *
	 * @todo The constructor to create a SQLStatement should reject strings
	 * containing semicolons, since compound statements are not handled by
	 * step() properly. There should be some other class SQLMultiStatement or
	 * something, which can then executed using a wrapper around sqlite3_exec.
	 */
	class SQLStatement;

	/**
	 * Throws a SQLiteException with the current sqlite3_errmsg passed
	 * to the constructor of the exception.
	 *
	 * This is essentially to save typing.
	 *
	 * @throws SQLiteException whenever called
	 */
	void throw_sqlite_exception();

	/**
	 * Executes a string on the database connection.
	 * This should be used only where the developer has complete
	 * control of the string being passed, to prevent SQL injection
	 * attacks. Generally, the functions provided by SQLStatement should
	 * be the preferred means for building and executing SQL statements.
	 */
	void execute_sql(std::string const& str);

	/**
	 * To find primary key of a table.
	 *
	 * Note, this function should not be used where \c table_name is
	 * an untrusted string.
	 *
	 * @todo To speed execution, assuming the return value for a given
	 * \c table_name never changes (is this a safe assumption?), the
	 * return values could be cached either in a
	 * map< string, vector<string> >, or by way of templatizing this
	 * function with \c table_name as a (non-type) template parameter,
	 * and the return value stored as a static variable inside the
	 * function.
	 *
	 * @returns a vector of the names of all the columns making up the
	 * primary key of the table named \c table_name. An empty vector
	 * is returned if there is no primary key.
	 *
	 * @throws SQLiteException in case of invalid table name or other
	 * SQL execution error.
	 *
	 * @param table_name name of table
	 */
	std::vector<std::string> primary_key(std::string const& table_name);

private:

	/**
	 * A connection to a SQLite3 database file.
	 *
	 * (Note this is a raw pointer not a smart pointer
	 * to facilitate more straightforward interaction with the SQLite
	 * C API.)
	 */
	sqlite3* m_connection;

	/**
	 * Create application-specific tables or perform other setup
	 * when database is opened.
	 *
	 * This function should be redefined in derived class if required
	 * to perform application-specific database setup.
	 *
	 * @throws sqloxx::SQLiteException if operation
	 * fails.
	 */
	virtual void setup();

};

class DatabaseConnection::SQLStatement:
	private boost::noncopyable
{
public:

	SQLStatement(DatabaseConnection& dbconn, std::string const& str);

	~SQLStatement();

	// Wrapper around SQLite bind functions
	void bind(std::string const& parameter_name, double value);
	void bind(std::string const& parameter_name, int value);
	void bind(std::string const& parameter_name, boost::int64_t value);
	void bind(std::string const& parameter_name, std::string const& str);

	/**
	 * Where a SQLStatement has a result set available,
	 * this function (template) can be used to extract the value at
	 * the \c indexth column of the current row (where \c index starts
	 * counting at 0).
	 *
	 * Currently the following types for T are supported:\n
	 *	\c boost::int64_t\n
	 *	int\n
	 *	double\n
	 *	std::string\n
	 *
	 * @param index is the column number (starting at 0) from which to
	 * read the value.
	 *
	 * @throws SQLiteException if:\n
	 * 	the index is out of range; or\n
	 * 	the requested column contains type that is incompatible with T.
	 */
	template <typename T>
	T extract(int index);

	// Wraps sqlite3_step
	// Returns true as long as there are further steps to go.
	bool step();

	// For executing statements which are not expected to return a result
	// set. SQLiteException is thrown if there are one or more result rows.
	void quick_step();

private:
	sqlite3_stmt* m_statement;
	DatabaseConnection& m_database_connection;

	// Return index no. of named parameter in statement
	int parameter_index(std::string const& parameter_name) const;

	/**
	 * Checks whether a column is available for extraction at
	 * index \c index, of type \c value_type, and throws an
	 * exception if not.
	 *
	 * @param index Position of column (starts from zero) in result
	 * row.
	 *
	 * @param value_type Should be a SQLite value type code, i.e. one of:\n
	 * 	SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, SQLITE_NULL.
	 *
	 * @throws SQLiteException if:\n
	 * 	There are no results available for extraction;\n
	 * 	\c index is out of range; or\n
	 * 	\c the value at position \c index is not of value type \c value_type.
	 */
	void check_column(int index, int value_type);

	// Check code is SQLITE_OK and if not finalize statement and
	// throw SQLiteException.
	void check_ok(int err_code);

};

// FUNCTION TEMPLATE DEFINITIONS

template <>
inline
int
DatabaseConnection::SQLStatement::extract<int>(int index)
{
	check_column(index, SQLITE_INTEGER);
	return sqlite3_column_int(m_statement, index);
}

template <>
inline
boost::int64_t
DatabaseConnection::SQLStatement::extract<boost::int64_t>(int index)
{
	check_column(index, SQLITE_INTEGER);
	return sqlite3_column_int64(m_statement, index);
}

template <>
inline
double
DatabaseConnection::SQLStatement::extract<double>(int index)
{
	check_column(index, SQLITE_FLOAT);
	return sqlite3_column_double(m_statement, index);
}

template <>
inline
std::string
DatabaseConnection::SQLStatement::extract<std::string>(int index)
{
	check_column(index, SQLITE_TEXT);
	const unsigned char* begin = sqlite3_column_text(m_statement, index);
	const unsigned char* end = begin;
	while (*end != '\0') ++end;
	return std::string(begin, end);
}

}  // namespace sqloxx

#endif  // GUARD_database_connection_hpp
<|endoftext|>


<commit_before>/*
 * This file is part of the UnTech Editor Suite.
 * Copyright (c) 2016 - 2018, Marcus Rowe <undisbeliever@gmail.com>.
 * Distributed under The MIT License: https://opensource.org/licenses/MIT
 */

#include "sigraphicsscene.h"
#include "accessors.h"
#include "document.h"
#include "siframegraphicsitem.h"
#include "gui-qt/common/graphics/resizableaabbgraphicsitem.h"
#include "gui-qt/metasprite/layersettings.h"
#include "gui-qt/metasprite/style.h"

#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <functional>

using namespace UnTech::GuiQt::MetaSprite::SpriteImporter;

SiGraphicsScene::SiGraphicsScene(LayerSettings* layerSettings, QWidget* parent)
    : QGraphicsScene(parent)
    , _layerSettings(layerSettings)
    , _frameContextMenu(new QMenu())
    , _style(new Style(parent))
    , _frameSetPixmap(new QGraphicsPixmapItem())
    , _paletteOutline(new QGraphicsPathItem())
    , _document(nullptr)
    , _inUpdateSelection(false)
    , _inOnSceneSelectionChanged(false)
{
    Q_ASSERT(_frameContextMenu != nullptr);
    Q_ASSERT(_layerSettings != nullptr);

    _frameSetPixmap->setTransformationMode(Qt::FastTransformation);
    _frameSetPixmap->setZValue(PIXMAP_ZVALUE);
    addItem(_frameSetPixmap);

    _paletteOutline->setPen(_style->paletteOutlinePen());
    _paletteOutline->setVisible(false);
    _paletteOutline->setZValue(PALETTE_ZVALUE);
    addItem(_paletteOutline);

    onLayerSettingsChanged();

    connect(this, &SiGraphicsScene::selectionChanged,
            this, &SiGraphicsScene::onSceneSelectionChanged);

    connect(_layerSettings, &LayerSettings::layerSettingsChanged,
            this, &SiGraphicsScene::onLayerSettingsChanged);
}

void SiGraphicsScene::setDocument(Document* document)
{
    if (_document == document) {
        return;
    }

    if (_document != nullptr) {
        _document->disconnect(this);
        _document->frameMap()->disconnect(this);
        _document->frameObjectList()->disconnect(this);
        _document->actionPointList()->disconnect(this);
        _document->entityHitboxList()->disconnect(this);
    }
    _document = document;

    // reset scene
    removeAllFrameItems();
    _frameSetPixmap->setPixmap(QPixmap());
    _paletteOutline->setPath(QPainterPath());
    _paletteOutline->setVisible(false);
    setSceneRect(QRect(0, 0, 0, 0));

    if (_document) {
        updateFrameSetPixmap();
        updatePaletteOutline();
        buildFrameItems();
        onSelectedFrameChanged();

        updateFrameObjectSelection();
        updateActionPointSelection();
        updateEntityHitboxSelection();
        updateTileHitboxSelection();

        connect(_document->frameMap(), &FrameMap::selectedItemChanged,
                this, &SiGraphicsScene::onSelectedFrameChanged);

        connect(_document, &Document::externalFilesModified,
                this, &SiGraphicsScene::updateFrameSetPixmap);
        connect(_document, &Document::frameSetPaletteChanged,
                this, &SiGraphicsScene::updatePaletteOutline);
        connect(_document, &Document::frameSetGridChanged,
                this, &SiGraphicsScene::onFrameSetGridChanged);

        connect(_document->frameMap(), &FrameMap::itemAdded,
                this, &SiGraphicsScene::onFrameAdded);
        connect(_document->frameMap(), &FrameMap::itemAboutToBeRemoved,
                this, &SiGraphicsScene::onFrameAboutToBeRemoved);

        connect(_document->frameMap(), &FrameMap::frameLocationChanged,
                this, &SiGraphicsScene::onFrameLocationChanged);
        connect(_document->frameMap(), &FrameMap::dataChanged,
                this, &SiGraphicsScene::onFrameDataChanged);

        connect(_document->frameObjectList(), &FrameObjectList::selectedIndexesChanged,
                this, &SiGraphicsScene::updateFrameObjectSelection);
        connect(_document->actionPointList(), &ActionPointList::selectedIndexesChanged,
                this, &SiGraphicsScene::updateActionPointSelection);
        connect(_document->entityHitboxList(), &EntityHitboxList::selectedIndexesChanged,
                this, &SiGraphicsScene::updateEntityHitboxSelection);

        connect(_document->frameMap(), &FrameMap::tileHitboxSelectedChanged,
                this, &SiGraphicsScene::updateTileHitboxSelection);

        connect(_document->frameObjectList(), &FrameObjectList::dataChanged,
                this, &SiGraphicsScene::onFrameObjectChanged);
        connect(_document->actionPointList(), &ActionPointList::dataChanged,
                this, &SiGraphicsScene::onActionPointChanged);
        connect(_document->entityHitboxList(), &EntityHitboxList::dataChanged,
                this, &SiGraphicsScene::onEntityHitboxChanged);

        // This class uses listChanged signal as it is more efficient when multiple items change

        connect(_document->frameObjectList(), &FrameObjectList::listChanged,
                this, &SiGraphicsScene::onFrameObjectListChanged);
        connect(_document->actionPointList(), &ActionPointList::listChanged,
                this, &SiGraphicsScene::onActionPointListChanged);
        connect(_document->entityHitboxList(), &EntityHitboxList::listChanged,
                this, &SiGraphicsScene::onEntityHitboxListChanged);
    }
}

void SiGraphicsScene::drawForeground(QPainter* painter, const QRectF& rect)
{
    if (_document == nullptr) {
        return;
    }

    const SI::Frame* frame = _document->frameMap()->selectedFrame();
    if (frame != nullptr) {
        const urect& fLoc = frame->location.aabb;

        painter->save();

        QPainterPath outer;
        outer.addRect(rect);

        QPainterPath inner;
        inner.addRect(fLoc.x, fLoc.y, fLoc.width, fLoc.height);

        QPainterPath path = outer.subtracted(inner);

        painter->setPen(Qt::NoPen);
        painter->setBrush(_style->antiHighlightBrush());

        painter->drawPath(path);

        painter->restore();
    }
}

void SiGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
    QGraphicsScene::mouseReleaseEvent(event);

    if (_document && event->button() == Qt::LeftButton) {
        commitMovedItems();

        // Set selected frame to the one under the mouse.
        // This is done manually as making SiFrameGraphicsItem selectable
        // complicates the selection model.

        auto frameItemAt = [this](const QPointF pos) -> SiFrameGraphicsItem* {
            for (QGraphicsItem* item : this->items(pos)) {
                auto* frameItem = dynamic_cast<SiFrameGraphicsItem*>(item);
                if (frameItem) {
                    return frameItem;
                }
            }
            return nullptr;
        };
        auto* press = frameItemAt(event->buttonDownScenePos(Qt::LeftButton));
        auto* release = frameItemAt(event->scenePos());

        if (press == release) {
            if (press) {
                idstring frameId = _document->frameSet()->frames.getId(press->frame());
                _document->frameMap()->setSelectedId(frameId);
            }
            else {
                _document->frameMap()->unselectItem();
            }
        }
    }
}
void SiGraphicsScene::onSelectedFrameChanged()
{
    const SI::Frame* frame = _document->frameMap()->selectedFrame();

    for (SiFrameGraphicsItem* item : _frameItems) {
        bool s = item->frame() == frame;
        item->setFrameSelected(s);

        // Selected frame is always on top
        item->setZValue(s ? SELECTED_FRAME_ZVALUE : FRAME_ZVALUE);
    }

    update();
}

template <typename F>
void SiGraphicsScene::updateSelection(F method,
                                      const vectorset<size_t>& selectedIndexes)
{
    if (_document == nullptr || _inOnSceneSelectionChanged) {
        return;
    }

    Q_ASSERT(_inUpdateSelection == false);
    _inUpdateSelection = true;

    const SI::Frame* frame = _document->frameMap()->selectedFrame();
    SiFrameGraphicsItem* frameItem = _frameItems.value(frame);

    if (frameItem != nullptr) {
        auto f = std::mem_fn(method);
        f(frameItem, selectedIndexes);
    }

    _inUpdateSelection = false;
}

void SiGraphicsScene::updateFrameObjectSelection()
{
    updateSelection(&SiFrameGraphicsItem::updateFrameObjectSelection,
                    _document->frameObjectList()->selectedIndexes());
}

void SiGraphicsScene::updateActionPointSelection()
{
    updateSelection(&SiFrameGraphicsItem::updateActionPointSelection,
                    _document->actionPointList()->selectedIndexes());
}

void SiGraphicsScene::updateEntityHitboxSelection()
{
    updateSelection(&SiFrameGraphicsItem::updateEntityHitboxSelection,
                    _document->entityHitboxList()->selectedIndexes());
}

void SiGraphicsScene::updateTileHitboxSelection()
{
    if (_inOnSceneSelectionChanged) {
        return;
    }

    Q_ASSERT(_inUpdateSelection == false);
    _inUpdateSelection = true;

    const SI::Frame* frame = _document->frameMap()->selectedFrame();
    bool hbSelected = _document->frameMap()->isTileHitboxSelected();

    SiFrameGraphicsItem* frameItem = _frameItems.value(frame);
    if (frameItem) {
        frameItem->updateTileHitboxSelected(hbSelected);
    }

    _inUpdateSelection = false;
}

void SiGraphicsScene::onSceneSelectionChanged()
{
    if (_inUpdateSelection || _document == nullptr) {
        return;
    }

    const SI::Frame* frame = _document->frameMap()->selectedFrame();
    const SiFrameGraphicsItem* frameItem = _frameItems.value(frame);

    if (frameItem == nullptr) {
        return;
    }

    Q_ASSERT(_inOnSceneSelectionChanged == false);
    _inOnSceneSelectionChanged = true;

    // Only the selected frame has selectable QGraphicsItems
    // No need to worry about items from different frames being selected

    auto getSelected = [](const auto& items) {
        std::vector<size_t> sel;

        for (int i = 0; i < items.size(); i++) {
            const QGraphicsItem* item = items.at(i);
            if (item->isSelected()) {
                sel.push_back(i);
            }
        }

        return std::move(sel);
    };

    _document->frameObjectList()->setSelectedIndexes(getSelected(frameItem->objects()));
    _document->actionPointList()->setSelectedIndexes(getSelected(frameItem->actionPoints()));
    _document->entityHitboxList()->setSelectedIndexes(getSelected(frameItem->entityHitboxes()));

    _document->frameMap()->setTileHitboxSelected(frameItem->tileHitbox()->isSelected());

    _inOnSceneSelectionChanged = false;
}

void SiGraphicsScene::updateFrameSetPixmap()
{
    if (_document && _document->frameSet()->imageFilename.empty() == false) {
        const std::string& fn = _document->frameSet()->imageFilename;

        QPixmap p(QString::fromStdString(fn), "PNG");
        _frameSetPixmap->setPixmap(p);
    }
    else {
        _frameSetPixmap->setPixmap(QPixmap());
    }

    updatePaletteOutline();

    setSceneRect(itemsBoundingRect());
}

void SiGraphicsScene::updatePaletteOutline()
{
    const SI::FrameSet* frameSet = _document->frameSet();
    const auto& palette = frameSet->palette;

    if (palette.usesUserSuppliedPalette()) {
        const unsigned colorSize = palette.colorSize;
        const usize paletteSize = palette.paletteSize();
        const QSize imageSize = _frameSetPixmap->pixmap().size();

        QPainterPath path;
        path.addRect(0, 0, paletteSize.width, paletteSize.height);
        for (unsigned i = 0; i < palette.nPalettes; i++) {
            path.moveTo(0, i * colorSize);
            path.lineTo(paletteSize.width, i * colorSize);
        }

        _paletteOutline->setPath(path);
        _paletteOutline->setPos(0, imageSize.height() - paletteSize.height);
        _paletteOutline->setVisible(true);
    }
    else {
        _paletteOutline->setPath(QPainterPath());
        _paletteOutline->setVisible(false);
    }

    setSceneRect(itemsBoundingRect());
}

void SiGraphicsScene::removeAllFrameItems()
{
    for (SiFrameGraphicsItem* frameItem : _frameItems) {
        removeItem(frameItem);
        delete frameItem;
    }
    _frameItems.clear();
}

void SiGraphicsScene::buildFrameItems()
{
    removeAllFrameItems();

    if (_document) {
        for (auto it : _document->frameSet()->frames) {
            SI::Frame* frame = &it.second;

            auto* frameItem = new SiFrameGraphicsItem(frame, _frameContextMenu.data(), _style);
            _frameItems.insert(frame, frameItem);
            frameItem->setVisible(FRAME_ZVALUE);
            addItem(frameItem);
        }
    }

    setSceneRect(itemsBoundingRect());
}

void SiGraphicsScene::onLayerSettingsChanged()
{
    for (SiFrameGraphicsItem* item : _frameItems) {
        item->updateLayerSettings(_layerSettings);
    }
}

void SiGraphicsScene::onFrameSetGridChanged()
{
    for (SiFrameGraphicsItem* frameItem : _frameItems) {
        frameItem->updateFrameLocation();
    }

    setSceneRect(itemsBoundingRect());
}

void SiGraphicsScene::onFrameAdded(const idstring& id)
{
    SI::Frame* frame = _document->frameSet()->frames.getPtr(id);
    if (frame) {
        auto* frameItem = new SiFrameGraphicsItem(frame, _frameContextMenu.data(), _style);
        _frameItems.insert(frame, frameItem);
        frameItem->setVisible(FRAME_ZVALUE);
        addItem(frameItem);
    }
}

void SiGraphicsScene::onFrameAboutToBeRemoved(const idstring& frameId)
{
    const SI::Frame* frame = _document->frameSet()->frames.getPtr(frameId);

    auto it = _frameItems.find(frame);
    if (it != _frameItems.end()) {
        SiFrameGraphicsItem* frameItem = it.value();
        _frameItems.erase(it);

        removeItem(frameItem);
        delete frameItem;
    }
}

void SiGraphicsScene::onFrameLocationChanged(const void* framePtr)
{
    auto it = _frameItems.find(framePtr);
    if (it != _frameItems.end()) {
        it.value()->updateFrameLocation();
    }

    setSceneRect(itemsBoundingRect());
    update();
}

void SiGraphicsScene::onFrameDataChanged(const void* framePtr)
{
    auto it = _frameItems.find(framePtr);
    if (it != _frameItems.end()) {
        it.value()->onFrameDataChanged();
    }
}

#define FRAME_CHILDREN_SLOTS(CLS)               \
    void SiGraphicsScene::on##CLS##Changed(     \
        const void* framePtr, size_t index)     \
    {                                           \
        auto it = _frameItems.find(framePtr);   \
        if (it != _frameItems.end()) {          \
            it.value()->update##CLS(index);     \
        }                                       \
    }                                           \
                                                \
    void SiGraphicsScene::on##CLS##ListChanged( \
        const void* framePtr)                   \
    {                                           \
        auto it = _frameItems.find(framePtr);   \
        if (it != _frameItems.end()) {          \
            it.value()->on##CLS##ListChanged(); \
        }                                       \
    }
FRAME_CHILDREN_SLOTS(FrameObject)
FRAME_CHILDREN_SLOTS(ActionPoint)
FRAME_CHILDREN_SLOTS(EntityHitbox)
<commit_msg>Fix segfault after deleting a Sprite Importer frame<commit_after>/*
 * This file is part of the UnTech Editor Suite.
 * Copyright (c) 2016 - 2018, Marcus Rowe <undisbeliever@gmail.com>.
 * Distributed under The MIT License: https://opensource.org/licenses/MIT
 */

#include "sigraphicsscene.h"
#include "accessors.h"
#include "document.h"
#include "siframegraphicsitem.h"
#include "gui-qt/common/graphics/resizableaabbgraphicsitem.h"
#include "gui-qt/metasprite/layersettings.h"
#include "gui-qt/metasprite/style.h"

#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <functional>

using namespace UnTech::GuiQt::MetaSprite::SpriteImporter;

SiGraphicsScene::SiGraphicsScene(LayerSettings* layerSettings, QWidget* parent)
    : QGraphicsScene(parent)
    , _layerSettings(layerSettings)
    , _frameContextMenu(new QMenu())
    , _style(new Style(parent))
    , _frameSetPixmap(new QGraphicsPixmapItem())
    , _paletteOutline(new QGraphicsPathItem())
    , _document(nullptr)
    , _inUpdateSelection(false)
    , _inOnSceneSelectionChanged(false)
{
    Q_ASSERT(_frameContextMenu != nullptr);
    Q_ASSERT(_layerSettings != nullptr);

    _frameSetPixmap->setTransformationMode(Qt::FastTransformation);
    _frameSetPixmap->setZValue(PIXMAP_ZVALUE);
    addItem(_frameSetPixmap);

    _paletteOutline->setPen(_style->paletteOutlinePen());
    _paletteOutline->setVisible(false);
    _paletteOutline->setZValue(PALETTE_ZVALUE);
    addItem(_paletteOutline);

    onLayerSettingsChanged();

    connect(this, &SiGraphicsScene::selectionChanged,
            this, &SiGraphicsScene::onSceneSelectionChanged);

    connect(_layerSettings, &LayerSettings::layerSettingsChanged,
            this, &SiGraphicsScene::onLayerSettingsChanged);
}

void SiGraphicsScene::setDocument(Document* document)
{
    if (_document == document) {
        return;
    }

    if (_document != nullptr) {
        _document->disconnect(this);
        _document->frameMap()->disconnect(this);
        _document->frameObjectList()->disconnect(this);
        _document->actionPointList()->disconnect(this);
        _document->entityHitboxList()->disconnect(this);
    }
    _document = document;

    // reset scene
    removeAllFrameItems();
    _frameSetPixmap->setPixmap(QPixmap());
    _paletteOutline->setPath(QPainterPath());
    _paletteOutline->setVisible(false);
    setSceneRect(QRect(0, 0, 0, 0));

    if (_document) {
        updateFrameSetPixmap();
        updatePaletteOutline();
        buildFrameItems();
        onSelectedFrameChanged();

        updateFrameObjectSelection();
        updateActionPointSelection();
        updateEntityHitboxSelection();
        updateTileHitboxSelection();

        connect(_document->frameMap(), &FrameMap::selectedItemChanged,
                this, &SiGraphicsScene::onSelectedFrameChanged);

        connect(_document, &Document::externalFilesModified,
                this, &SiGraphicsScene::updateFrameSetPixmap);
        connect(_document, &Document::frameSetPaletteChanged,
                this, &SiGraphicsScene::updatePaletteOutline);
        connect(_document, &Document::frameSetGridChanged,
                this, &SiGraphicsScene::onFrameSetGridChanged);

        connect(_document->frameMap(), &FrameMap::itemAdded,
                this, &SiGraphicsScene::onFrameAdded);
        connect(_document->frameMap(), &FrameMap::itemAboutToBeRemoved,
                this, &SiGraphicsScene::onFrameAboutToBeRemoved);

        connect(_document->frameMap(), &FrameMap::frameLocationChanged,
                this, &SiGraphicsScene::onFrameLocationChanged);
        connect(_document->frameMap(), &FrameMap::dataChanged,
                this, &SiGraphicsScene::onFrameDataChanged);

        connect(_document->frameObjectList(), &FrameObjectList::selectedIndexesChanged,
                this, &SiGraphicsScene::updateFrameObjectSelection);
        connect(_document->actionPointList(), &ActionPointList::selectedIndexesChanged,
                this, &SiGraphicsScene::updateActionPointSelection);
        connect(_document->entityHitboxList(), &EntityHitboxList::selectedIndexesChanged,
                this, &SiGraphicsScene::updateEntityHitboxSelection);

        connect(_document->frameMap(), &FrameMap::tileHitboxSelectedChanged,
                this, &SiGraphicsScene::updateTileHitboxSelection);

        connect(_document->frameObjectList(), &FrameObjectList::dataChanged,
                this, &SiGraphicsScene::onFrameObjectChanged);
        connect(_document->actionPointList(), &ActionPointList::dataChanged,
                this, &SiGraphicsScene::onActionPointChanged);
        connect(_document->entityHitboxList(), &EntityHitboxList::dataChanged,
                this, &SiGraphicsScene::onEntityHitboxChanged);

        // This class uses listChanged signal as it is more efficient when multiple items change

        connect(_document->frameObjectList(), &FrameObjectList::listChanged,
                this, &SiGraphicsScene::onFrameObjectListChanged);
        connect(_document->actionPointList(), &ActionPointList::listChanged,
                this, &SiGraphicsScene::onActionPointListChanged);
        connect(_document->entityHitboxList(), &EntityHitboxList::listChanged,
                this, &SiGraphicsScene::onEntityHitboxListChanged);
    }
}

void SiGraphicsScene::drawForeground(QPainter* painter, const QRectF& rect)
{
    if (_document == nullptr) {
        return;
    }

    const SI::Frame* frame = _document->frameMap()->selectedFrame();
    if (frame != nullptr) {
        const urect& fLoc = frame->location.aabb;

        painter->save();

        QPainterPath outer;
        outer.addRect(rect);

        QPainterPath inner;
        inner.addRect(fLoc.x, fLoc.y, fLoc.width, fLoc.height);

        QPainterPath path = outer.subtracted(inner);

        painter->setPen(Qt::NoPen);
        painter->setBrush(_style->antiHighlightBrush());

        painter->drawPath(path);

        painter->restore();
    }
}

void SiGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
    QGraphicsScene::mouseReleaseEvent(event);

    if (_document && event->button() == Qt::LeftButton) {
        commitMovedItems();

        // Set selected frame to the one under the mouse.
        // This is done manually as making SiFrameGraphicsItem selectable
        // complicates the selection model.

        auto frameItemAt = [this](const QPointF pos) -> SiFrameGraphicsItem* {
            for (QGraphicsItem* item : this->items(pos)) {
                auto* frameItem = dynamic_cast<SiFrameGraphicsItem*>(item);
                if (frameItem) {
                    return frameItem;
                }
            }
            return nullptr;
        };
        auto* press = frameItemAt(event->buttonDownScenePos(Qt::LeftButton));
        auto* release = frameItemAt(event->scenePos());

        if (press == release) {
            if (press) {
                idstring frameId = _document->frameSet()->frames.getId(press->frame());
                _document->frameMap()->setSelectedId(frameId);
            }
            else {
                _document->frameMap()->unselectItem();
            }
        }
    }
}
void SiGraphicsScene::onSelectedFrameChanged()
{
    const SI::Frame* frame = _document->frameMap()->selectedFrame();

    for (SiFrameGraphicsItem* item : _frameItems) {
        bool s = item->frame() == frame;
        item->setFrameSelected(s);

        // Selected frame is always on top
        item->setZValue(s ? SELECTED_FRAME_ZVALUE : FRAME_ZVALUE);
    }

    update();
}

template <typename F>
void SiGraphicsScene::updateSelection(F method,
                                      const vectorset<size_t>& selectedIndexes)
{
    if (_document == nullptr || _inOnSceneSelectionChanged) {
        return;
    }

    Q_ASSERT(_inUpdateSelection == false);
    _inUpdateSelection = true;

    const SI::Frame* frame = _document->frameMap()->selectedFrame();
    SiFrameGraphicsItem* frameItem = _frameItems.value(frame);

    if (frameItem != nullptr) {
        auto f = std::mem_fn(method);
        f(frameItem, selectedIndexes);
    }

    _inUpdateSelection = false;
}

void SiGraphicsScene::updateFrameObjectSelection()
{
    updateSelection(&SiFrameGraphicsItem::updateFrameObjectSelection,
                    _document->frameObjectList()->selectedIndexes());
}

void SiGraphicsScene::updateActionPointSelection()
{
    updateSelection(&SiFrameGraphicsItem::updateActionPointSelection,
                    _document->actionPointList()->selectedIndexes());
}

void SiGraphicsScene::updateEntityHitboxSelection()
{
    updateSelection(&SiFrameGraphicsItem::updateEntityHitboxSelection,
                    _document->entityHitboxList()->selectedIndexes());
}

void SiGraphicsScene::updateTileHitboxSelection()
{
    if (_inOnSceneSelectionChanged) {
        return;
    }

    Q_ASSERT(_inUpdateSelection == false);
    _inUpdateSelection = true;

    const SI::Frame* frame = _document->frameMap()->selectedFrame();
    bool hbSelected = _document->frameMap()->isTileHitboxSelected();

    SiFrameGraphicsItem* frameItem = _frameItems.value(frame);
    if (frameItem) {
        frameItem->updateTileHitboxSelected(hbSelected);
    }

    _inUpdateSelection = false;
}

void SiGraphicsScene::onSceneSelectionChanged()
{
    if (_inUpdateSelection || _document == nullptr) {
        return;
    }

    const SI::Frame* frame = _document->frameMap()->selectedFrame();
    const SiFrameGraphicsItem* frameItem = _frameItems.value(frame);

    if (frameItem == nullptr) {
        return;
    }

    Q_ASSERT(_inOnSceneSelectionChanged == false);
    _inOnSceneSelectionChanged = true;

    // Only the selected frame has selectable QGraphicsItems
    // No need to worry about items from different frames being selected

    auto getSelected = [](const auto& items) {
        std::vector<size_t> sel;

        for (int i = 0; i < items.size(); i++) {
            const QGraphicsItem* item = items.at(i);
            if (item->isSelected()) {
                sel.push_back(i);
            }
        }

        return std::move(sel);
    };

    _document->frameObjectList()->setSelectedIndexes(getSelected(frameItem->objects()));
    _document->actionPointList()->setSelectedIndexes(getSelected(frameItem->actionPoints()));
    _document->entityHitboxList()->setSelectedIndexes(getSelected(frameItem->entityHitboxes()));

    _document->frameMap()->setTileHitboxSelected(frameItem->tileHitbox()->isSelected());

    _inOnSceneSelectionChanged = false;
}

void SiGraphicsScene::updateFrameSetPixmap()
{
    if (_document && _document->frameSet()->imageFilename.empty() == false) {
        const std::string& fn = _document->frameSet()->imageFilename;

        QPixmap p(QString::fromStdString(fn), "PNG");
        _frameSetPixmap->setPixmap(p);
    }
    else {
        _frameSetPixmap->setPixmap(QPixmap());
    }

    updatePaletteOutline();

    setSceneRect(itemsBoundingRect());
}

void SiGraphicsScene::updatePaletteOutline()
{
    const SI::FrameSet* frameSet = _document->frameSet();
    const auto& palette = frameSet->palette;

    if (palette.usesUserSuppliedPalette()) {
        const unsigned colorSize = palette.colorSize;
        const usize paletteSize = palette.paletteSize();
        const QSize imageSize = _frameSetPixmap->pixmap().size();

        QPainterPath path;
        path.addRect(0, 0, paletteSize.width, paletteSize.height);
        for (unsigned i = 0; i < palette.nPalettes; i++) {
            path.moveTo(0, i * colorSize);
            path.lineTo(paletteSize.width, i * colorSize);
        }

        _paletteOutline->setPath(path);
        _paletteOutline->setPos(0, imageSize.height() - paletteSize.height);
        _paletteOutline->setVisible(true);
    }
    else {
        _paletteOutline->setPath(QPainterPath());
        _paletteOutline->setVisible(false);
    }

    setSceneRect(itemsBoundingRect());
}

void SiGraphicsScene::removeAllFrameItems()
{
    for (SiFrameGraphicsItem* frameItem : _frameItems) {
        // cannot call `this->removeItem(frameItem);` here (despite the Qt docs
        // recomending it) as it causes a use after free segfault.
        delete frameItem;
    }
    _frameItems.clear();
}

void SiGraphicsScene::buildFrameItems()
{
    removeAllFrameItems();

    if (_document) {
        for (auto it : _document->frameSet()->frames) {
            SI::Frame* frame = &it.second;

            auto* frameItem = new SiFrameGraphicsItem(frame, _frameContextMenu.data(), _style);
            _frameItems.insert(frame, frameItem);
            frameItem->setVisible(FRAME_ZVALUE);
            addItem(frameItem);
        }
    }

    setSceneRect(itemsBoundingRect());
}

void SiGraphicsScene::onLayerSettingsChanged()
{
    for (SiFrameGraphicsItem* item : _frameItems) {
        item->updateLayerSettings(_layerSettings);
    }
}

void SiGraphicsScene::onFrameSetGridChanged()
{
    for (SiFrameGraphicsItem* frameItem : _frameItems) {
        frameItem->updateFrameLocation();
    }

    setSceneRect(itemsBoundingRect());
}

void SiGraphicsScene::onFrameAdded(const idstring& id)
{
    SI::Frame* frame = _document->frameSet()->frames.getPtr(id);
    if (frame) {
        auto* frameItem = new SiFrameGraphicsItem(frame, _frameContextMenu.data(), _style);
        _frameItems.insert(frame, frameItem);
        frameItem->setVisible(FRAME_ZVALUE);
        addItem(frameItem);
    }
}

void SiGraphicsScene::onFrameAboutToBeRemoved(const idstring& frameId)
{
    const SI::Frame* frame = _document->frameSet()->frames.getPtr(frameId);

    auto it = _frameItems.find(frame);
    if (it != _frameItems.end()) {
        SiFrameGraphicsItem* frameItem = it.value();
        _frameItems.erase(it);

        // cannot call `this->removeItem(frameItem);` here (despite the Qt docs
        // recomending it) as it causes a use after free segfault.
        delete frameItem;
    }
}

void SiGraphicsScene::onFrameLocationChanged(const void* framePtr)
{
    auto it = _frameItems.find(framePtr);
    if (it != _frameItems.end()) {
        it.value()->updateFrameLocation();
    }

    setSceneRect(itemsBoundingRect());
    update();
}

void SiGraphicsScene::onFrameDataChanged(const void* framePtr)
{
    auto it = _frameItems.find(framePtr);
    if (it != _frameItems.end()) {
        it.value()->onFrameDataChanged();
    }
}

#define FRAME_CHILDREN_SLOTS(CLS)               \
    void SiGraphicsScene::on##CLS##Changed(     \
        const void* framePtr, size_t index)     \
    {                                           \
        auto it = _frameItems.find(framePtr);   \
        if (it != _frameItems.end()) {          \
            it.value()->update##CLS(index);     \
        }                                       \
    }                                           \
                                                \
    void SiGraphicsScene::on##CLS##ListChanged( \
        const void* framePtr)                   \
    {                                           \
        auto it = _frameItems.find(framePtr);   \
        if (it != _frameItems.end()) {          \
            it.value()->on##CLS##ListChanged(); \
        }                                       \
    }
FRAME_CHILDREN_SLOTS(FrameObject)
FRAME_CHILDREN_SLOTS(ActionPoint)
FRAME_CHILDREN_SLOTS(EntityHitbox)
<|endoftext|>


<commit_before>//  Copyright 2010 Gregory Szorc
//
//  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 distributed 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 <zippylog/zippylog.hpp>
#include <zippylog/envelope.hpp>
#include <zippylog/platform.hpp>
#include <zippylog/stream.hpp>

#include <iostream>
#include <sstream>
#include <string>
#include <vector>

using ::std::cerr;
using ::std::cout;
using ::std::endl;
using ::std::ostringstream;
using ::std::string;
using ::std::vector;
using ::zippylog::Envelope;
using ::zippylog::InputStream;

class ZippylogcatSource {
public:
    enum Type {
        FILENAME = 1,
    };

    Type type;
    string filename;
};

class ZippylogcatParams {
public:
    vector<ZippylogcatSource> sources;
};

static bool ParseCommandArguments(
    vector<string> args,
    ZippylogcatParams &params,
    string &error)
{
    if (args.size() < 2) {
        ostringstream usage;
        usage
            << "Usage: " << args[0] << " [options] source0 [source1...]"          << endl
            <<                                                                       endl
            << "Each source can be one of the following:"                         << endl
            <<                                                                       endl
            << "    * A filename. It is assumed the file is a zippylog stream"    << endl
            << "    * A directory. It is assumed the directory is a stream set"   << endl
            << "      which contains multiple zippylog stream files"              << endl
            << "    * A 0MQ socket endpoint to a zippylog server plus store path" << endl
            << "      identifier. The store path is defined after a pound"        << endl
            << "      character (#). e.g. 'tcp://127.0.0.1:52483#/bucketA'"       << endl
            <<                                                                       endl
            << "Sources will be read in full in the order they are defined."      << endl
            <<                                                                       endl
            << "The following arguments control behavior:"                        << endl
            <<                                                                       endl
        ;
        error = usage.str();

        return false;
    }

    // get rid of first element, the program name
    args.erase(args.begin());

    for (int i = 0; i < args.size(); i++) {
        if (args[i][0] == '-') {
            error = "unrecognized argument: " + args[i];
            return false;
        }
    }

    for (int i = 0; i < args.size(); i++) {
        string arg = args[i];

        ZippylogcatSource source;

        // if it looks like a URI
        if (arg.find("://") != string::npos) {

        }
        else {
            ::zippylog::platform::FileStat st;
            if (!::zippylog::platform::stat(arg, st)) {
                error = "file path does not seem to exist or isn't readable: " + arg;
                return false;
            }

            source.type = ZippylogcatSource::FILENAME;
            source.filename = arg;

            params.sources.push_back(source);
        }
    }

    return true;
}

bool ProcessFilename(string filename)
{
    try {
        InputStream is(filename);

        uint32 read = 0;
        Envelope e;
        while (is.ReadEnvelope(e, read)) {
            cout << e.ToString();
        }
    }
    catch (::std::exception e) {
        cerr << "Exception when processing file: " << e.what() << endl;
        return false;
    }
}

int main(int argc, const char * const argv[])
{
    try {
        ::zippylog::initialize_library();

        vector<string> args;
        for (int i = 0; i < argc; i++) {
            args.push_back(argv[i]);
        }

        string error;
        ZippylogcatParams params;

        if (!ParseCommandArguments(args, params, error)) {
            cout << error << endl;
            return 1;
        }

        for (int i = 0; i < params.sources.size(); i++) {
            ZippylogcatSource source = params.sources[i];

            if (source.type == ZippylogcatSource::FILENAME) {
                if (!ProcessFilename(source.filename)) break;
            }
        }

        ::zippylog::shutdown_library();
    }
    catch (string s) {
        cout << "Exception:" << endl;
        cout << s;
        return 1;
    }
    catch (char * s) {
        cout << "Exception: " << s << endl;
        return 1;
    }
    catch (...) {
        cout << "received an exception" << endl;
        return 1;
    }

    return 0;
}
<commit_msg>reflect stream refactoring<commit_after>//  Copyright 2010 Gregory Szorc
//
//  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 distributed 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 <zippylog/zippylog.hpp>
#include <zippylog/envelope.hpp>
#include <zippylog/platform.hpp>
#include <zippylog/stream.hpp>

#include <iostream>
#include <sstream>
#include <string>
#include <vector>

using ::std::cerr;
using ::std::cout;
using ::std::endl;
using ::std::ostringstream;
using ::std::string;
using ::std::vector;
using ::zippylog::Envelope;
using ::zippylog::FileInputStream;

class ZippylogcatSource {
public:
    enum Type {
        FILENAME = 1,
    };

    Type type;
    string filename;
};

class ZippylogcatParams {
public:
    vector<ZippylogcatSource> sources;
};

static bool ParseCommandArguments(
    vector<string> args,
    ZippylogcatParams &params,
    string &error)
{
    if (args.size() < 2) {
        ostringstream usage;
        usage
            << "Usage: " << args[0] << " [options] source0 [source1...]"          << endl
            <<                                                                       endl
            << "Each source can be one of the following:"                         << endl
            <<                                                                       endl
            << "    * A filename. It is assumed the file is a zippylog stream"    << endl
            << "    * A directory. It is assumed the directory is a stream set"   << endl
            << "      which contains multiple zippylog stream files"              << endl
            << "    * A 0MQ socket endpoint to a zippylog server plus store path" << endl
            << "      identifier. The store path is defined after a pound"        << endl
            << "      character (#). e.g. 'tcp://127.0.0.1:52483#/bucketA'"       << endl
            <<                                                                       endl
            << "Sources will be read in full in the order they are defined."      << endl
            <<                                                                       endl
            << "The following arguments control behavior:"                        << endl
            <<                                                                       endl
        ;
        error = usage.str();

        return false;
    }

    // get rid of first element, the program name
    args.erase(args.begin());

    for (int i = 0; i < args.size(); i++) {
        if (args[i][0] == '-') {
            error = "unrecognized argument: " + args[i];
            return false;
        }
    }

    for (int i = 0; i < args.size(); i++) {
        string arg = args[i];

        ZippylogcatSource source;

        // if it looks like a URI
        if (arg.find("://") != string::npos) {

        }
        else {
            ::zippylog::platform::FileStat st;
            if (!::zippylog::platform::stat(arg, st)) {
                error = "file path does not seem to exist or isn't readable: " + arg;
                return false;
            }

            source.type = ZippylogcatSource::FILENAME;
            source.filename = arg;

            params.sources.push_back(source);
        }
    }

    return true;
}

bool ProcessFilename(string filename)
{
    try {
        FileInputStream is(filename);

        uint32 read = 0;
        Envelope e;
        while (is.ReadEnvelope(e, read)) {
            cout << e.ToString();
        }
    }
    catch (::std::exception e) {
        cerr << "Exception when processing file: " << e.what() << endl;
        return false;
    }
}

int main(int argc, const char * const argv[])
{
    try {
        ::zippylog::initialize_library();

        vector<string> args;
        for (int i = 0; i < argc; i++) {
            args.push_back(argv[i]);
        }

        string error;
        ZippylogcatParams params;

        if (!ParseCommandArguments(args, params, error)) {
            cout << error << endl;
            return 1;
        }

        for (int i = 0; i < params.sources.size(); i++) {
            ZippylogcatSource source = params.sources[i];

            if (source.type == ZippylogcatSource::FILENAME) {
                if (!ProcessFilename(source.filename)) break;
            }
        }

        ::zippylog::shutdown_library();
    }
    catch (string s) {
        cout << "Exception:" << endl;
        cout << s;
        return 1;
    }
    catch (char * s) {
        cout << "Exception: " << s << endl;
        return 1;
    }
    catch (...) {
        cout << "received an exception" << endl;
        return 1;
    }

    return 0;
}
<|endoftext|>


<commit_before>#ifndef __TEST__MODELS__MODEL_TEST_FIXTURE_HPP__
#define __TEST__MODELS__MODEL_TEST_FIXTURE_HPP__

#include <gtest/gtest.h>
#include <stan/mcmc/chains.hpp>
#include <utility>
#include <boost/math/distributions/students_t.hpp>

/**
 * Model_Test_Fixture is a test fixture for google test
 * to aid in running models.
 *
 * Derived classes must define:
 *   static std::vector<std::string> get_model_path()
 *   static bool has_data()
 *   - indicates whether the model has data
 */
template <class Derived>
class Model_Test_Fixture : public ::testing::Test {

public:
  static char path_separator;
  static std::string model_path;
  static stan::mcmc::chains<> *chains;
  static size_t num_chains;

  /**
   * SetUpTestCase() called by google test once
   * a test case.
   *
   * Sets:
   *  - path_separator: '\' for Windows, '/' for all else.
   *  - base_name:      location of model without ".stan"
   *  - chains:         creates a chains object by reading
   *                    the header of a csv file
   */
  static void SetUpTestCase() {
    path_separator = get_path_separator();
    model_path = convert_model_path(get_model_path());

    chains = create_chains();
  }

  /**
   * TearDownTestCase() called by google test once
   * a test case.
   *
   * Deletes chains.
   */
  static void TearDownTestCase() {
    delete chains;
  }

  /**
   * Gets the path separator for the OS.
   *
   * @return '\' for Windows, '/' otherwise.
   */
  static char get_path_separator() {
    char c;
    FILE *in;
    if(!(in = popen("make path_separator --no-print-directory", "r")))
      throw std::runtime_error("\"make path_separator\" has failed.");
    c = fgetc(in);
    pclose(in);
    return c;
  }

  /**
   * Returns the path as a string with the appropriate
   * path separator.
   *
   * @param model_path vector of strings representing path to the model
   *
   * @return the string representation of the path with the appropriate
   *    path separator.
   */
  static std::string
  convert_model_path(const std::vector<std::string> model_path) {
    std::string path;
    if (model_path.size() > 0) {
      path.append(model_path[0]);
      for (size_t i = 1; i < model_path.size(); i++) {
        path.append(1, path_separator);
        path.append(model_path[i]);
      }
    }
    return path;
  }

  /**
   * Returns the csv file for the chain number.
   *
   * @param chain the chain number
   *
   * @return the file location of the csv file
   */
  static std::string get_csv_file(size_t chain) {
    std::stringstream csv_file;
    csv_file << model_path << "." << chain << ".csv";
    return csv_file.str();
  }

  /**
   * Returns the command to run in a shell on the OS.
   *
   * @param chain the chain number
   *
   * @return a string command that can be run from a shell.
   */
  static std::string get_command(size_t chain) {
    std::stringstream command;
    command << model_path;
    command << " --samples=" << get_csv_file(chain);
    if (has_data()) {
      command << " --data=" << model_path << ".Rdata";
    }
    command << " --refresh=2000";
    return command.str();
  }

  /**
   * Populates the chains object with data from csv files.
   */
  static void populate_chains() {
    if (chains->num_kept_samples() == 0U) {
      for (size_t chain = 0U; chain < num_chains; chain++) {
        stan::mcmc::add_chain(*chains, chain, get_csv_file(chain), 2U);
      }
    }
  }

  /**
   * Runs the model num_chains times.
   * Populates the chains object after running the model.
   */
  static void run_model() {
    for (size_t chain = 0; chain < num_chains; chain++) {
      std::string command = get_command(chain);
      EXPECT_EQ(0, system(command.c_str()))
        << "Can not execute command: " << command << std::endl;
    }
    populate_chains();
  }

  /**
   * Creates a chains object.
   *
   * Runs the model for 0 iterations to read the
   * names and dimensions of the parameters.
   *
   * @return An initialized chains object.
   */
  static stan::mcmc::chains<>* create_chains() {
    std::string command = get_command(0U);
    command += " --iter=0";
    EXPECT_EQ(0, system(command.c_str()))
      << "Can not build header using: " << command << std::endl;

    std::vector<std::string> names;
    std::vector<std::vector<size_t> > dimss;
    stan::mcmc::read_variables(get_csv_file(0U), 2,
                               names, dimss);

    return (new stan::mcmc::chains<>(num_chains, names, dimss));
  }

  /**
   * Return the path to the model (without the extension) as
   * a vector.
   *
   * @return the path to the model
   */
  static std::vector<std::string> get_model_path() {
    return Derived::get_model_path();
  }

  /**
   * Return true if the model has data.
   *
   * @return true if the model has data;
   *         false otherwise.
   */
  static bool has_data() {
    return Derived::has_data();
  }

  static std::vector<std::pair<size_t, double> >
  get_expected_values() {
    return Derived::get_expected_values();
  }
};

template<class Derived>
char Model_Test_Fixture<Derived>::path_separator;

template<class Derived>
stan::mcmc::chains<> *Model_Test_Fixture<Derived>::chains;

template<class Derived>
size_t Model_Test_Fixture<Derived>::num_chains = 2;

template<class Derived>
std::string Model_Test_Fixture<Derived>::model_path;

TYPED_TEST_CASE_P(Model_Test_Fixture);

TYPED_TEST_P(Model_Test_Fixture, RunModel) {
  TypeParam::run_model();
}

TYPED_TEST_P(Model_Test_Fixture, ChainsTest) {
  stan::mcmc::chains<> *c = TypeParam::chains;
  size_t num_chains = c->num_chains();
  size_t num_params = c->num_params();
  for (size_t chain = 0; chain < num_chains; chain++) {
    for (size_t param = 0; param < num_params; param++) {
      EXPECT_TRUE(c->variance(chain, param) > 0)
	<< "Chain " << chain << ", param " << param
	<< ": variance is 0";
    }
  }
}

TYPED_TEST_P(Model_Test_Fixture, ExpectedValuesTest) {
  using boost::math::students_t;
  using boost::math::quantile;

  using std::vector;
  using std::pair;

  vector<pair<size_t, double> > expected_values = TypeParam::get_expected_values();
  //std::cout << "testing " << expected_values.size() << " values" << std::endl;
  for (size_t i = 0; i < expected_values.size(); i++) {
    size_t index = expected_values[i].first;
    double e_val = expected_values[i].second;

    double neff = TypeParam::chains->effective_sample_size(index);
    double mean = TypeParam::chains->mean(index);
    // FIXME: chains->variance(index) crashes.
    double se = TypeParam::chains->sd(index) / std::sqrt(neff);
    double T = quantile(students_t(neff-1.0), 0.975);
    EXPECT_NEAR(e_val, mean, T*se)
      << "For variable " << index << ", "
      << "T is: " << T << " and se is: " << se << std::endl;
    // FIXME: better error message
  }
}

REGISTER_TYPED_TEST_CASE_P(Model_Test_Fixture,
			   RunModel,
			   ChainsTest,
			   ExpectedValuesTest);

#endif
<commit_msg>test-models: updated expected values test<commit_after>#ifndef __TEST__MODELS__MODEL_TEST_FIXTURE_HPP__
#define __TEST__MODELS__MODEL_TEST_FIXTURE_HPP__

#include <gtest/gtest.h>
#include <stan/mcmc/chains.hpp>
#include <utility>
#include <boost/math/distributions/students_t.hpp>
#include <boost/math/distributions/binomial.hpp>

/**
 * Model_Test_Fixture is a test fixture for google test
 * to aid in running models.
 *
 * Derived classes must define:
 *   static std::vector<std::string> get_model_path()
 *   static bool has_data()
 *   - indicates whether the model has data
 */
template <class Derived>
class Model_Test_Fixture : public ::testing::Test {

public:
  static char path_separator;
  static std::string model_path;
  static stan::mcmc::chains<> *chains;
  static size_t num_chains;

  /**
   * SetUpTestCase() called by google test once
   * a test case.
   *
   * Sets:
   *  - path_separator: '\' for Windows, '/' for all else.
   *  - base_name:      location of model without ".stan"
   *  - chains:         creates a chains object by reading
   *                    the header of a csv file
   */
  static void SetUpTestCase() {
    path_separator = get_path_separator();
    model_path = convert_model_path(get_model_path());

    chains = create_chains();
  }

  /**
   * TearDownTestCase() called by google test once
   * a test case.
   *
   * Deletes chains.
   */
  static void TearDownTestCase() {
    delete chains;
  }

  /**
   * Gets the path separator for the OS.
   *
   * @return '\' for Windows, '/' otherwise.
   */
  static char get_path_separator() {
    char c;
    FILE *in;
    if(!(in = popen("make path_separator --no-print-directory", "r")))
      throw std::runtime_error("\"make path_separator\" has failed.");
    c = fgetc(in);
    pclose(in);
    return c;
  }

  /**
   * Returns the path as a string with the appropriate
   * path separator.
   *
   * @param model_path vector of strings representing path to the model
   *
   * @return the string representation of the path with the appropriate
   *    path separator.
   */
  static std::string
  convert_model_path(const std::vector<std::string> model_path) {
    std::string path;
    if (model_path.size() > 0) {
      path.append(model_path[0]);
      for (size_t i = 1; i < model_path.size(); i++) {
        path.append(1, path_separator);
        path.append(model_path[i]);
      }
    }
    return path;
  }

  /**
   * Returns the csv file for the chain number.
   *
   * @param chain the chain number
   *
   * @return the file location of the csv file
   */
  static std::string get_csv_file(size_t chain) {
    std::stringstream csv_file;
    csv_file << model_path << "." << chain << ".csv";
    return csv_file.str();
  }

  /**
   * Returns the command to run in a shell on the OS.
   *
   * @param chain the chain number
   *
   * @return a string command that can be run from a shell.
   */
  static std::string get_command(size_t chain) {
    std::stringstream command;
    command << model_path;
    command << " --samples=" << get_csv_file(chain);
    if (has_data()) {
      command << " --data=" << model_path << ".Rdata";
    }
    command << " --refresh=2000";
    return command.str();
  }

  /**
   * Populates the chains object with data from csv files.
   */
  static void populate_chains() {
    if (chains->num_kept_samples() == 0U) {
      for (size_t chain = 0U; chain < num_chains; chain++) {
        stan::mcmc::add_chain(*chains, chain, get_csv_file(chain), 2U);
      }
    }
  }

  /**
   * Runs the model num_chains times.
   * Populates the chains object after running the model.
   */
  static void run_model() {
    for (size_t chain = 0; chain < num_chains; chain++) {
      std::string command = get_command(chain);
      EXPECT_EQ(0, system(command.c_str()))
        << "Can not execute command: " << command << std::endl;
    }
    populate_chains();
  }

  /**
   * Creates a chains object.
   *
   * Runs the model for 0 iterations to read the
   * names and dimensions of the parameters.
   *
   * @return An initialized chains object.
   */
  static stan::mcmc::chains<>* create_chains() {
    std::string command = get_command(0U);
    command += " --iter=0";
    EXPECT_EQ(0, system(command.c_str()))
      << "Can not build header using: " << command << std::endl;

    std::vector<std::string> names;
    std::vector<std::vector<size_t> > dimss;
    stan::mcmc::read_variables(get_csv_file(0U), 2,
                               names, dimss);

    return (new stan::mcmc::chains<>(num_chains, names, dimss));
  }

  /**
   * Return the path to the model (without the extension) as
   * a vector.
   *
   * @return the path to the model
   */
  static std::vector<std::string> get_model_path() {
    return Derived::get_model_path();
  }

  /**
   * Return true if the model has data.
   *
   * @return true if the model has data;
   *         false otherwise.
   */
  static bool has_data() {
    return Derived::has_data();
  }

  static std::vector<std::pair<size_t, double> >
  get_expected_values() {
    return Derived::get_expected_values();
  }
};

template<class Derived>
char Model_Test_Fixture<Derived>::path_separator;

template<class Derived>
stan::mcmc::chains<> *Model_Test_Fixture<Derived>::chains;

template<class Derived>
size_t Model_Test_Fixture<Derived>::num_chains = 2;

template<class Derived>
std::string Model_Test_Fixture<Derived>::model_path;

TYPED_TEST_CASE_P(Model_Test_Fixture);

TYPED_TEST_P(Model_Test_Fixture, RunModel) {
  TypeParam::run_model();
}

TYPED_TEST_P(Model_Test_Fixture, ChainsTest) {
  stan::mcmc::chains<> *c = TypeParam::chains;
  size_t num_chains = c->num_chains();
  size_t num_params = c->num_params();
  for (size_t chain = 0; chain < num_chains; chain++) {
    for (size_t param = 0; param < num_params; param++) {
      EXPECT_TRUE(c->variance(chain, param) > 0)
        << "Chain " << chain << ", param " << param
        << ": variance is 0";
    }
  }
}

TYPED_TEST_P(Model_Test_Fixture, ExpectedValuesTest) {
  using std::vector;
  using std::pair;
  using std::sqrt;
  using std::abs;
  using std::setw;

  using boost::math::students_t;
  using boost::math::binomial;
  using boost::math::quantile;

  vector<pair<size_t, double> > expected_values = TypeParam::get_expected_values();
  size_t n = expected_values.size();
  if (n == 0)
    return;

  stan::mcmc::chains<> *c = TypeParam::chains;
  double alpha = 0.05;
  if (n < 3)
    alpha = 0.01;

  int failed = 0;
  std::stringstream err_message;
  for (size_t i = 0; i < n; i++) {
    size_t index = expected_values[i].first;
    double expected_mean = expected_values[i].second;

    double neff = c->effective_sample_size(index);
    double sample_mean = c->mean(index);
    double se = c->sd(index) / sqrt(neff);
    double z = quantile(students_t(neff-1.0), 1 - alpha/2.0);

    if (abs(expected_mean - sample_mean) > z*se) {
      failed++;
      // want the error message to have which, what, how
      err_message << "parameter index: " << index
                  << "\n\texpected:    " << setw(10) << expected_mean
                  << "\n\tsampled:     " << setw(10) << sample_mean
                  << "\n\tneff:        " << setw(10) << neff
                  << "\n\tsplit R.hat: " << setw(10) << c->split_potential_scale_reduction(index)
                  << "\n\tz:           " << setw(10) << z
                  << "\n\tse:          " << setw(10) << se
                  << "\n\n\tabs(diff) > z * se: "
                  << abs(expected_mean - sample_mean) << " > " << z*se << "\n\n";
    }
  }

  if (failed == 0)
    return;

  double p = 1 - cdf(binomial(n, alpha), failed);
  // this test should fail less than 0.1% of the time.
  if (p < 0.001) {
    EXPECT_EQ(0, failed)
      << "Failed " << failed << " of " << expected_values.size() << " comparisons\n"
      << "p: " << p << std::endl
      << "------------------------------------------------------------\n"
      << err_message.str();
  }
}

REGISTER_TYPED_TEST_CASE_P(Model_Test_Fixture,
                           RunModel,
                           ChainsTest,
                           ExpectedValuesTest);

#endif
<|endoftext|>