<commit_before>/*
 */
#include <synext_macro.hpp>
#include "../parse/common.hpp"
#include "../parse/parseerror.hpp"
#include "../parse/tokentree.hpp"
#include "../parse/lex.hpp"
#include <ast/crate.hpp>    // for m_load_std

namespace {

    struct FmtArgs
    {
        enum class Align {
            Unspec,
            Left,
            Center,
            Right,
        };
        enum class Sign {
            Unspec,
            Plus,
            Minus,
        };

        Align   align = Align::Unspec;
        char    align_char = ' ';

        Sign    sign = Sign::Unspec;
        bool    alternate = false;
        bool    zero_pad = false;

        bool width_is_arg = false;
        unsigned int width = 0;

        bool prec_is_arg = false;
        unsigned int prec = 0;
    };

    struct FmtFrag
    {
        ::std::string   leading_text;

        unsigned int    arg_index;

        const char* trait_name;
        // TODO: Support case where this hasn't been edited (telling the formatter that it has nothing to apply)
        FmtArgs     args;
    };

    ::std::tuple< ::std::vector<FmtFrag>, ::std::string> parse_format_string(const Span& sp, const ::std::string& format_string, const ::std::map< ::std::string,unsigned int>& named, unsigned int n_free)
    {
        unsigned int n_named = named.size();
        unsigned int next_free = 0;

        ::std::vector<FmtFrag>  frags;
        ::std::string   cur_literal;

        const char* s = format_string.c_str();
        for( ; *s; s ++)
        {
            if( *s != '{' )
            {
                if( *s == '}' ) {
                    s ++;
                    if( *s != '}' )
                        ERROR(sp, E0000, "'}' must be escaped as '}}' in format strings");
                    // - fall with *s == '}'
                }
                cur_literal += *s;
            }
            else
            {
                s ++;
                if( *s == '{' ) {
                    cur_literal += '{';
                    continue ;
                }

                unsigned int index = ~0u;
                const char* trait_name;
                FmtArgs args;

                // Formatting parameter
                if( *s != ':' && *s != '}' ) {
                    // Parse either an integer or an identifer
                    TODO(sp, "Parse named/positional formatting fragment at \"" << s);
                }
                else {
                    // Leave (for now)
                    // - If index is ~0u at the end of this block, it's set to the next arg
                    // - This allows {:.*} to format correctly (taking <prec> then <arg>)
                }

                // If next character is ':', parse extra information
                if( *s == ':' ) {
                    s ++;   // eat ':'

                    // Alignment
                    if( s[0] != '\0' && (s[1] == '<' || s[1] == '^' || s[1] == '>') ) {
                        args.align_char = s[0];
                        s ++;
                    }
                    if( *s == '<' ) {
                        args.align = FmtArgs::Align::Left;
                        s ++;
                    }
                    else if( *s == '^' ) {
                        args.align = FmtArgs::Align::Center;
                        s ++;
                    }
                    else if( *s == '>' ) {
                        args.align = FmtArgs::Align::Right;
                        s ++;
                    }
                    else {
                        //args.align = FmtArgs::Align::Unspec;
                    }

                    // Sign
                    if( *s == '+' ) {
                        args.sign = FmtArgs::Sign::Plus;
                        s ++;
                    }
                    else if( *s == '-' ) {
                        args.sign = FmtArgs::Sign::Minus;
                        s ++;
                    }
                    else {
                        args.sign = FmtArgs::Sign::Unspec;
                    }

                    if( *s == '#' ) {
                        args.alternate = true;
                        s ++;
                    }
                    else {
                        //args.alternate = false;
                    }

                    if( *s == '0' ) {
                        args.zero_pad = true;
                        s ++;
                    }
                    else {
                        //args.zero_pad = false;
                    }

                    // Padded width
                    if( ::std::isdigit(*s) /*|| *s == '*'*/ ) {
                        unsigned int val = 0;
                        while( ::std::isdigit(*s) )
                        {
                            val *= 10;
                            val += *s - '0';
                            s ++;
                        }
                        args.width = val;

                        if( *s == '$' ) {
                            args.width_is_arg = true;
                            s ++;
                        }
                        else {
                            //args.width_is_arg = false;
                        }
                    }
                    // Precision
                    if( *s == '.' ) {
                        s ++;
                        // '*' - Use next argument
                        if( *s == '*' ) {
                            args.prec_is_arg = true;
                            if( next_free == n_free ) {
                                ERROR(sp, E0000, "Not enough arguments passed, expected at least " << n_free+1);
                            }
                            args.prec = next_free + n_named;
                            next_free ++;
                        }
                        else {
                            unsigned int val = 0;
                            while( ::std::isdigit(*s) )
                            {
                                val *= 10;
                                val += *s - '0';
                                s ++;
                            }
                            args.prec = val;

                            if( *s == '$' ) {
                                args.prec_is_arg = true;
                                s ++;
                            }
                            else {
                                //args.prec_is_arg = false;
                            }
                        }
                    }

                    // Parse ident?
                    // - Lazy way is to just handle a single char and ensure that it is just a single char
                    if( s[0] != '}' && s[0] != '\0' && s[1] != '}' ) {
                        TODO(sp, "Parse formatting fragment at \"" << s << "\" (long type)");
                    }

                    switch(s[0])
                    {
                    case '\0':
                        ERROR(sp, E0000, "Unexpected end of formatting string");
                    default:
                        ERROR(sp, E0000, "Unknown formatting type specifier '" << *s << "'");
                    case '}':      trait_name = "Display"; break;
                    case '?': s++; trait_name = "Debug"  ; break;
                    case 'b': s++; trait_name = "Binary"; break;
                    case 'o': s++; trait_name = "Octal" ; break;
                    case 'x': s++; trait_name = "LowerHex"; break;
                    case 'X': s++; trait_name = "UpperHex"; break;
                    case 'p': s++; trait_name = "Pointer" ; break;
                    case 'e': s++; trait_name = "LowerExp"; break;
                    case 'E': s++; trait_name = "UpperExp"; break;
                    }
                    assert(*s == '}');
                }
                else {
                    if( *s != '}' )
                        ERROR(sp, E0000, "Malformed formatting fragment, unexpected " << *s);
                    // Otherwise, it's just a trivial Display call
                    trait_name = "Display";
                }

                // Set index if unspecified
                if( index == ~0u )
                {
                    if( next_free == n_free ) {
                        ERROR(sp, E0000, "Not enough arguments passed, expected at least " << n_free+1);
                    }
                    index = next_free + n_named;
                    next_free ++;
                }

                frags.push_back( FmtFrag {
                    mv$(cur_literal),
                    index, trait_name,
                    mv$(args)
                    });
            }
        }

        return ::std::make_tuple( mv$(frags), mv$(cur_literal) );
    }
}

class CFormatArgsExpander:
    public ExpandProcMacro
{
    bool    expand_early() const override { return true; }

    ::std::unique_ptr<TokenStream> expand(const Span& sp, const ::AST::Crate& crate, const ::std::string& ident, const TokenTree& tt, AST::Module& mod) override
    {
        Token   tok;

        auto lex = TTStream(tt);
        if( ident != "" )
            ERROR(sp, E0000, "format_args! doesn't take an ident");

        auto n = Parse_ExprVal(lex);
        auto format_string = dynamic_cast<AST::ExprNode_String&>(*n).m_value;

        ::std::map< ::std::string, unsigned int>   named_args_index;
        ::std::vector<TokenTree>    named_args;
        ::std::vector<TokenTree>    free_args;

        // - Parse the arguments
        while( GET_TOK(tok, lex) == TOK_COMMA )
        {
            // - Named parameters
            if( lex.lookahead(0) == TOK_IDENT && lex.lookahead(1) == TOK_EQUAL )
            {
                GET_CHECK_TOK(tok, lex, TOK_IDENT);
                auto name = mv$(tok.str());

                GET_CHECK_TOK(tok, lex, TOK_EQUAL);

                auto expr_tt = Parse_TT_Expr(lex);

                auto ins_rv = named_args_index.insert( ::std::make_pair(mv$(name), named_args.size()) );
                if( ins_rv.second == false ) {
                    ERROR(sp, E0000, "Duplicate definition of named argument `" << ins_rv.first->first << "`");
                }
                named_args.push_back( mv$(expr_tt) );
            }
            // - Free parameters
            else
            {
                auto expr_tt = Parse_TT_Expr(lex);
                free_args.push_back( mv$(expr_tt) );
            }
        }

        // - Parse the format string
        ::std::vector< FmtFrag> fragments;
        ::std::string   tail;
        ::std::tie( fragments, tail ) = parse_format_string(sp, format_string,  named_args_index, free_args.size());

        // TODO: Properly expand format_args! (requires mangling to fit ::core::fmt::rt::v1)
        // - For now, just emits the text with no corresponding format fragments

        ::std::vector<TokenTree> toks;
        {
            // TODO: Figure out what path to use (::fmt, ::core::fmt, or ::std::fmt)

            switch(crate.m_load_std)
            {
            case ::AST::Crate::LOAD_NONE:
                break;
            case ::AST::Crate::LOAD_CORE:
                toks.push_back( TokenTree(TOK_DOUBLE_COLON) );
                toks.push_back( Token(TOK_STRING, "core") );
                break;
            case ::AST::Crate::LOAD_STD:
                toks.push_back( TokenTree(TOK_DOUBLE_COLON) );
                toks.push_back( Token(TOK_IDENT, "std") );
                break;
            }
            toks.push_back( TokenTree(TOK_DOUBLE_COLON) );
            toks.push_back( Token(TOK_IDENT, "fmt") );
            toks.push_back( TokenTree(TOK_DOUBLE_COLON) );
            toks.push_back( Token(TOK_IDENT, "Arguments") );
            toks.push_back( TokenTree(TOK_DOUBLE_COLON) );
            toks.push_back( Token(TOK_IDENT, "new_v1") );
            toks.push_back( TokenTree(TOK_PAREN_OPEN) );
            {
                toks.push_back( TokenTree(TOK_AMP) );
                toks.push_back( TokenTree(TOK_SQUARE_OPEN) );
                for(const auto& frag : fragments ) {
                    toks.push_back( Token(TOK_STRING, frag.leading_text) );
                    toks.push_back( TokenTree(TOK_COMMA) );
                }
                toks.push_back( TokenTree(TOK_SQUARE_CLOSE) );
                toks.push_back( TokenTree(TOK_COMMA) );

                toks.push_back( TokenTree(TOK_AMP) );
                toks.push_back( TokenTree(TOK_SQUARE_OPEN) );
                toks.push_back( TokenTree(TOK_SQUARE_CLOSE) );
            }
            toks.push_back( TokenTree(TOK_PAREN_CLOSE) );
        }

        return box$( TTStreamO(TokenTree(mv$(toks))) );
    }
};

STATIC_MACRO("format_args", CFormatArgsExpander);

<commit_msg>Expand format_args! - (minor) Commenting and TODO to actually emit the fragments<commit_after>/*
 */
#include <synext_macro.hpp>
#include "../parse/common.hpp"
#include "../parse/parseerror.hpp"
#include "../parse/tokentree.hpp"
#include "../parse/lex.hpp"
#include <ast/crate.hpp>    // for m_load_std

namespace {

    struct FmtArgs
    {
        enum class Align {
            Unspec,
            Left,
            Center,
            Right,
        };
        enum class Sign {
            Unspec,
            Plus,
            Minus,
        };

        Align   align = Align::Unspec;
        char    align_char = ' ';

        Sign    sign = Sign::Unspec;
        bool    alternate = false;
        bool    zero_pad = false;

        bool width_is_arg = false;
        unsigned int width = 0;

        bool prec_is_arg = false;
        unsigned int prec = 0;
    };

    struct FmtFrag
    {
        ::std::string   leading_text;

        unsigned int    arg_index;

        const char* trait_name;
        // TODO: Support case where this hasn't been edited (telling the formatter that it has nothing to apply)
        FmtArgs     args;
    };

    ::std::tuple< ::std::vector<FmtFrag>, ::std::string> parse_format_string(const Span& sp, const ::std::string& format_string, const ::std::map< ::std::string,unsigned int>& named, unsigned int n_free)
    {
        unsigned int n_named = named.size();
        unsigned int next_free = 0;

        ::std::vector<FmtFrag>  frags;
        ::std::string   cur_literal;

        const char* s = format_string.c_str();
        for( ; *s; s ++)
        {
            if( *s != '{' )
            {
                if( *s == '}' ) {
                    s ++;
                    if( *s != '}' )
                        ERROR(sp, E0000, "'}' must be escaped as '}}' in format strings");
                    // - fall with *s == '}'
                }
                cur_literal += *s;
            }
            else
            {
                s ++;
                if( *s == '{' ) {
                    cur_literal += '{';
                    continue ;
                }

                unsigned int index = ~0u;
                const char* trait_name;
                FmtArgs args;

                // Formatting parameter
                if( *s != ':' && *s != '}' ) {
                    // Parse either an integer or an identifer
                    TODO(sp, "Parse named/positional formatting fragment at \"" << s);
                }
                else {
                    // Leave (for now)
                    // - If index is ~0u at the end of this block, it's set to the next arg
                    // - This allows {:.*} to format correctly (taking <prec> then <arg>)
                }

                // If next character is ':', parse extra information
                if( *s == ':' ) {
                    s ++;   // eat ':'

                    // Alignment
                    if( s[0] != '\0' && (s[1] == '<' || s[1] == '^' || s[1] == '>') ) {
                        args.align_char = s[0];
                        s ++;
                    }
                    if( *s == '<' ) {
                        args.align = FmtArgs::Align::Left;
                        s ++;
                    }
                    else if( *s == '^' ) {
                        args.align = FmtArgs::Align::Center;
                        s ++;
                    }
                    else if( *s == '>' ) {
                        args.align = FmtArgs::Align::Right;
                        s ++;
                    }
                    else {
                        //args.align = FmtArgs::Align::Unspec;
                    }

                    // Sign
                    if( *s == '+' ) {
                        args.sign = FmtArgs::Sign::Plus;
                        s ++;
                    }
                    else if( *s == '-' ) {
                        args.sign = FmtArgs::Sign::Minus;
                        s ++;
                    }
                    else {
                        args.sign = FmtArgs::Sign::Unspec;
                    }

                    if( *s == '#' ) {
                        args.alternate = true;
                        s ++;
                    }
                    else {
                        //args.alternate = false;
                    }

                    if( *s == '0' ) {
                        args.zero_pad = true;
                        s ++;
                    }
                    else {
                        //args.zero_pad = false;
                    }

                    // Padded width
                    if( ::std::isdigit(*s) /*|| *s == '*'*/ ) {
                        unsigned int val = 0;
                        while( ::std::isdigit(*s) )
                        {
                            val *= 10;
                            val += *s - '0';
                            s ++;
                        }
                        args.width = val;

                        if( *s == '$' ) {
                            args.width_is_arg = true;
                            s ++;
                        }
                        else {
                            //args.width_is_arg = false;
                        }
                    }
                    // Precision
                    if( *s == '.' ) {
                        s ++;
                        // '*' - Use next argument
                        if( *s == '*' ) {
                            args.prec_is_arg = true;
                            if( next_free == n_free ) {
                                ERROR(sp, E0000, "Not enough arguments passed, expected at least " << n_free+1);
                            }
                            args.prec = next_free + n_named;
                            next_free ++;
                        }
                        else {
                            unsigned int val = 0;
                            while( ::std::isdigit(*s) )
                            {
                                val *= 10;
                                val += *s - '0';
                                s ++;
                            }
                            args.prec = val;

                            if( *s == '$' ) {
                                args.prec_is_arg = true;
                                s ++;
                            }
                            else {
                                //args.prec_is_arg = false;
                            }
                        }
                    }

                    // Parse ident?
                    // - Lazy way is to just handle a single char and ensure that it is just a single char
                    if( s[0] != '}' && s[0] != '\0' && s[1] != '}' ) {
                        TODO(sp, "Parse formatting fragment at \"" << s << "\" (long type)");
                    }

                    switch(s[0])
                    {
                    case '\0':
                        ERROR(sp, E0000, "Unexpected end of formatting string");
                    default:
                        ERROR(sp, E0000, "Unknown formatting type specifier '" << *s << "'");
                    case '}':      trait_name = "Display"; break;
                    case '?': s++; trait_name = "Debug"  ; break;
                    case 'b': s++; trait_name = "Binary"; break;
                    case 'o': s++; trait_name = "Octal" ; break;
                    case 'x': s++; trait_name = "LowerHex"; break;
                    case 'X': s++; trait_name = "UpperHex"; break;
                    case 'p': s++; trait_name = "Pointer" ; break;
                    case 'e': s++; trait_name = "LowerExp"; break;
                    case 'E': s++; trait_name = "UpperExp"; break;
                    }
                    assert(*s == '}');
                }
                else {
                    if( *s != '}' )
                        ERROR(sp, E0000, "Malformed formatting fragment, unexpected " << *s);
                    // Otherwise, it's just a trivial Display call
                    trait_name = "Display";
                }

                // Set index if unspecified
                if( index == ~0u )
                {
                    if( next_free == n_free ) {
                        ERROR(sp, E0000, "Not enough arguments passed, expected at least " << n_free+1);
                    }
                    index = next_free + n_named;
                    next_free ++;
                }

                frags.push_back( FmtFrag {
                    mv$(cur_literal),
                    index, trait_name,
                    mv$(args)
                    });
            }
        }

        return ::std::make_tuple( mv$(frags), mv$(cur_literal) );
    }
}

class CFormatArgsExpander:
    public ExpandProcMacro
{
    bool    expand_early() const override { return true; }

    ::std::unique_ptr<TokenStream> expand(const Span& sp, const ::AST::Crate& crate, const ::std::string& ident, const TokenTree& tt, AST::Module& mod) override
    {
        Token   tok;

        auto lex = TTStream(tt);
        if( ident != "" )
            ERROR(sp, E0000, "format_args! doesn't take an ident");

        auto n = Parse_ExprVal(lex);
        auto format_string = dynamic_cast<AST::ExprNode_String&>(*n).m_value;

        ::std::map< ::std::string, unsigned int>   named_args_index;
        ::std::vector<TokenTree>    named_args;
        ::std::vector<TokenTree>    free_args;

        // - Parse the arguments
        while( GET_TOK(tok, lex) == TOK_COMMA )
        {
            // - Named parameters
            if( lex.lookahead(0) == TOK_IDENT && lex.lookahead(1) == TOK_EQUAL )
            {
                GET_CHECK_TOK(tok, lex, TOK_IDENT);
                auto name = mv$(tok.str());

                GET_CHECK_TOK(tok, lex, TOK_EQUAL);

                auto expr_tt = Parse_TT_Expr(lex);

                auto ins_rv = named_args_index.insert( ::std::make_pair(mv$(name), named_args.size()) );
                if( ins_rv.second == false ) {
                    ERROR(sp, E0000, "Duplicate definition of named argument `" << ins_rv.first->first << "`");
                }
                named_args.push_back( mv$(expr_tt) );
            }
            // - Free parameters
            else
            {
                auto expr_tt = Parse_TT_Expr(lex);
                free_args.push_back( mv$(expr_tt) );
            }
        }

        // - Parse the format string
        ::std::vector< FmtFrag> fragments;
        ::std::string   tail;
        ::std::tie( fragments, tail ) = parse_format_string(sp, format_string,  named_args_index, free_args.size());

        // TODO: Properly expand format_args! (requires mangling to fit ::core::fmt::rt::v1)
        // - For now, just emits the text with no corresponding format fragments

        ::std::vector<TokenTree> toks;
        {
            switch(crate.m_load_std)
            {
            case ::AST::Crate::LOAD_NONE:
                break;
            case ::AST::Crate::LOAD_CORE:
                toks.push_back( TokenTree(TOK_DOUBLE_COLON) );
                toks.push_back( Token(TOK_STRING, "core") );
                break;
            case ::AST::Crate::LOAD_STD:
                toks.push_back( TokenTree(TOK_DOUBLE_COLON) );
                toks.push_back( Token(TOK_IDENT, "std") );
                break;
            }

            // ::fmt::Arguments::new_v1
            toks.push_back( TokenTree(TOK_DOUBLE_COLON) );
            toks.push_back( Token(TOK_IDENT, "fmt") );
            toks.push_back( TokenTree(TOK_DOUBLE_COLON) );
            toks.push_back( Token(TOK_IDENT, "Arguments") );
            toks.push_back( TokenTree(TOK_DOUBLE_COLON) );
            toks.push_back( Token(TOK_IDENT, "new_v1") );
            // (
            toks.push_back( TokenTree(TOK_PAREN_OPEN) );
            {
                toks.push_back( TokenTree(TOK_AMP) );
                // Raw string fragments
                toks.push_back( TokenTree(TOK_SQUARE_OPEN) );
                for(const auto& frag : fragments ) {
                    toks.push_back( Token(TOK_STRING, frag.leading_text) );
                    toks.push_back( TokenTree(TOK_COMMA) );
                }
                toks.push_back( TokenTree(TOK_SQUARE_CLOSE) );
                toks.push_back( TokenTree(TOK_COMMA) );

                // TODO: Fragments to format
                // - The format stored by mrustc doesn't quite work with how rustc (and fmt::rt::v1) works
                toks.push_back( TokenTree(TOK_AMP) );
                toks.push_back( TokenTree(TOK_SQUARE_OPEN) );
                toks.push_back( TokenTree(TOK_SQUARE_CLOSE) );
            }
            // )
            toks.push_back( TokenTree(TOK_PAREN_CLOSE) );
        }

        return box$( TTStreamO(TokenTree(mv$(toks))) );
    }
};

STATIC_MACRO("format_args", CFormatArgsExpander);

<|endoftext|>


<commit_before>/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * This file is part of the libkeynote project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <cmath>

#include "KNTransformation.h"

#include "KNTransformationTest.h"

namespace test
{

using libkeynote::KNTransformation;

void KNTransformationTest::setUp()
{
}

void KNTransformationTest::tearDown()
{
}

void KNTransformationTest::testConstruction()
{
  // identity
  CPPUNIT_ASSERT(KNTransformation() == KNTransformation(1, 0, 0, 1, 0, 0));

  using namespace libkeynote::transformations;

  // centering
  CPPUNIT_ASSERT(center(200, 100) == KNTransformation(1, 0, 0, 1, 100, 50));
  CPPUNIT_ASSERT(decenter(200, 100) == KNTransformation(1, 0, 0, 1, -100, -50));

  // flipping
  CPPUNIT_ASSERT(flip(true, false) == KNTransformation(-1, 0, 0, 1, 0, 0));
  CPPUNIT_ASSERT(flip(false, true) == KNTransformation(1, 0, 0, -1, 0, 0));
  CPPUNIT_ASSERT(flip(true, true) == KNTransformation(-1, 0, 0, -1, 0, 0));

  // rotating
  // CPPUNIT_ASSERT(rotate(M_PI / 2) == KNTransformation(0, -1, 1, 0, 0, 0));

  // scaling
  CPPUNIT_ASSERT(scale(2, 1) == KNTransformation(2, 0, 0, 1, 0, 0));
  CPPUNIT_ASSERT(scale(1, 2) == KNTransformation(1, 0, 0, 2, 0, 0));
  CPPUNIT_ASSERT(scale(3, 2) == KNTransformation(3, 0, 0, 2, 0, 0));

  // shearing
  // CPPUNIT_ASSERT(shear(M_PI / 4, 0) == KNTransformation(1, 2, 0, 1, 0, 0));
  // CPPUNIT_ASSERT(shear(0, M_PI / 4) == KNTransformation(1, 0, 2, 1, 0, 0));
  // CPPUNIT_ASSERT(shear(M_PI / 4, M_PI / 4) == KNTransformation(1, 2, 2, 1, 0, 0));

  // translating
  CPPUNIT_ASSERT(translate(100, 0) == KNTransformation(1, 0, 0, 1, 100, 0));
  CPPUNIT_ASSERT(translate(0, 100) == KNTransformation(1, 0, 0, 1, 0, 100));
  CPPUNIT_ASSERT(translate(300, 100) == KNTransformation(1, 0, 0, 1, 300, 100));
}

void KNTransformationTest::testConstructionIdentity()
{
  using namespace libkeynote::transformations;

  CPPUNIT_ASSERT(center(0, 0) == KNTransformation());
  CPPUNIT_ASSERT(decenter(0, 0) == KNTransformation());
  CPPUNIT_ASSERT(flip(false, false) == KNTransformation());
  CPPUNIT_ASSERT(rotate(0) == KNTransformation());
  // CPPUNIT_ASSERT(rotate(2 * M_PI) == KNTransformation());
  CPPUNIT_ASSERT(scale(1, 1) == KNTransformation());
  CPPUNIT_ASSERT(shear(0, 0) == KNTransformation());
  CPPUNIT_ASSERT(translate(0, 0) == KNTransformation());
}

void KNTransformationTest::testConstructionFromGeometry()
{
  // TODO: implement me
}

void KNTransformationTest::testIdentities()
{
  // TODO: implement me
}

void KNTransformationTest::testInverseOperations()
{
  // TODO: implement me
}

void KNTransformationTest::testMultiplication()
{
  // TODO: implement me
}

CPPUNIT_TEST_SUITE_REGISTRATION(KNTransformationTest);

}

/* vim:set shiftwidth=2 softtabstop=2 expandtab: */
<commit_msg>test mult. of transformations<commit_after>/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * This file is part of the libkeynote project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <cmath>

#include "KNTransformation.h"

#include "KNTransformationTest.h"

namespace test
{

using libkeynote::KNTransformation;

void KNTransformationTest::setUp()
{
}

void KNTransformationTest::tearDown()
{
}

void KNTransformationTest::testConstruction()
{
  // identity
  CPPUNIT_ASSERT(KNTransformation() == KNTransformation(1, 0, 0, 1, 0, 0));

  using namespace libkeynote::transformations;

  // centering
  CPPUNIT_ASSERT(center(200, 100) == KNTransformation(1, 0, 0, 1, 100, 50));
  CPPUNIT_ASSERT(decenter(200, 100) == KNTransformation(1, 0, 0, 1, -100, -50));

  // flipping
  CPPUNIT_ASSERT(flip(true, false) == KNTransformation(-1, 0, 0, 1, 0, 0));
  CPPUNIT_ASSERT(flip(false, true) == KNTransformation(1, 0, 0, -1, 0, 0));
  CPPUNIT_ASSERT(flip(true, true) == KNTransformation(-1, 0, 0, -1, 0, 0));

  // rotating
  // CPPUNIT_ASSERT(rotate(M_PI / 2) == KNTransformation(0, -1, 1, 0, 0, 0));

  // scaling
  CPPUNIT_ASSERT(scale(2, 1) == KNTransformation(2, 0, 0, 1, 0, 0));
  CPPUNIT_ASSERT(scale(1, 2) == KNTransformation(1, 0, 0, 2, 0, 0));
  CPPUNIT_ASSERT(scale(3, 2) == KNTransformation(3, 0, 0, 2, 0, 0));

  // shearing
  // CPPUNIT_ASSERT(shear(M_PI / 4, 0) == KNTransformation(1, 2, 0, 1, 0, 0));
  // CPPUNIT_ASSERT(shear(0, M_PI / 4) == KNTransformation(1, 0, 2, 1, 0, 0));
  // CPPUNIT_ASSERT(shear(M_PI / 4, M_PI / 4) == KNTransformation(1, 2, 2, 1, 0, 0));

  // translating
  CPPUNIT_ASSERT(translate(100, 0) == KNTransformation(1, 0, 0, 1, 100, 0));
  CPPUNIT_ASSERT(translate(0, 100) == KNTransformation(1, 0, 0, 1, 0, 100));
  CPPUNIT_ASSERT(translate(300, 100) == KNTransformation(1, 0, 0, 1, 300, 100));
}

void KNTransformationTest::testConstructionIdentity()
{
  using namespace libkeynote::transformations;

  CPPUNIT_ASSERT(center(0, 0) == KNTransformation());
  CPPUNIT_ASSERT(decenter(0, 0) == KNTransformation());
  CPPUNIT_ASSERT(flip(false, false) == KNTransformation());
  CPPUNIT_ASSERT(rotate(0) == KNTransformation());
  // CPPUNIT_ASSERT(rotate(2 * M_PI) == KNTransformation());
  CPPUNIT_ASSERT(scale(1, 1) == KNTransformation());
  CPPUNIT_ASSERT(shear(0, 0) == KNTransformation());
  CPPUNIT_ASSERT(translate(0, 0) == KNTransformation());
}

void KNTransformationTest::testConstructionFromGeometry()
{
  // TODO: implement me
}

void KNTransformationTest::testIdentities()
{
  // TODO: implement me
}

void KNTransformationTest::testInverseOperations()
{
  // TODO: implement me
}

void KNTransformationTest::testMultiplication()
{
  using namespace libkeynote::transformations;

  CPPUNIT_ASSERT(KNTransformation() * KNTransformation() == KNTransformation());

  CPPUNIT_ASSERT(KNTransformation() * KNTransformation(1, 2, 3, 4, 5, 6) == KNTransformation(1, 2, 3, 4, 5, 6));
  CPPUNIT_ASSERT(KNTransformation(1, 2, 3, 4, 5, 6) * KNTransformation() == KNTransformation(1, 2, 3, 4, 5, 6));
  CPPUNIT_ASSERT(KNTransformation(1, 2, 3, 4, 5, 6) * KNTransformation(6, 5, 4, 3, 2, 1) == KNTransformation(14, 11, 34, 27, 56, 44));
}

CPPUNIT_TEST_SUITE_REGISTRATION(KNTransformationTest);

}

/* vim:set shiftwidth=2 softtabstop=2 expandtab: */
<|endoftext|>


<commit_before>#ifndef FIRSTPERSONCONTROL_HPP_INCLUDED
#define FIRSTPERSONCONTROL_HPP_INCLUDED

#include "clock.hpp"

namespace gst
{
    class Input;
    class Spatial;

    class FirstPersonControl {
    public:
        FirstPersonControl();
        void update(float dt, Input const & input, Spatial & spatial);

        float rotation_speed;
        float movement_speed;
    private:
        void rotate(float dt, Input const & input, Spatial & spatial);
        void move(float dt, Input const & input, Spatial & spatial);

        float yaw_angle;
        float pitch_angle;
    };
}

#endif
<commit_msg>make first-person control to a subclass to spatial control<commit_after>#ifndef FIRSTPERSONCONTROL_HPP_INCLUDED
#define FIRSTPERSONCONTROL_HPP_INCLUDED

#include "spatialcontrol.hpp"

namespace gst
{
    // The responsibility of this class is to imitate the behavior of
    // first-person camera control.
    class FirstPersonControl : public SpatialControl {
    public:
        FirstPersonControl();
        void update(float dt, Input const & input, Spatial & spatial) override;

        float rotation_speed; // also known as the mouse sensitivity
        float movement_speed;
    private:
        void rotate(float dt, Input const & input, Spatial & spatial);
        void move(float dt, Input const & input, Spatial & spatial);

        float yaw_angle;
        float pitch_angle;
    };
}

#endif
<|endoftext|>


<commit_before>/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                    _______    _                                     *
 *                   (  ____ \  ( \     |\     /|                      *
 *                   | (    \/  | (     ( \   / )                      *
 *                   | (__      | |      \ (_) /                       *
 *                   |  __)     | |       \   /                        *
 *                   | (        | |        ) (                         *
 *                   | )        | (____/\  | |                         *
 *                   |/         (_______/  \_/                         *
 *                                                                     *
 *                                                                     *
 *     fly is an awesome c++11 network library.                        *
 *                                                                     *
 *   @author: lichuan                                                  *
 *   @qq: 308831759                                                    *
 *   @email: 308831759@qq.com                                          *
 *   @github: https://github.com/lichuan/fly                           *
 *   @date: 2015-06-22 18:22:00                                        *
 *                                                                     *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <unistd.h>
#include <cstring>
#include <netinet/in.h>
#include "fly/net/connection.hpp"
#include "fly/net/poller_task.hpp"
#include "fly/base/logger.hpp"

namespace fly {
namespace net {

fly::base::ID_Allocator Connection::m_id_allocator;

Connection::~Connection()
{
    while(auto *message_chunk = m_recv_msg_queue.pop())
    {
        delete message_chunk;
    }

    while(auto *message_chunk = m_send_msg_queue.pop())
    {
        delete message_chunk;
    }
}

Connection::Connection(int32 fd, const Addr &peer_addr)
{
    m_fd = fd;
    m_peer_addr = peer_addr;
}

uint64 Connection::id()
{
    return m_id;
}

void Connection::send(rapidjson::Document &doc)
{
    rapidjson::StringBuffer buffer;
    rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
    doc.Accept(writer);
    send(buffer.GetString(), buffer.GetSize());
}

void Connection::send(const void *data, uint32 size)
{
    Message_Chunk *message_chunk = new Message_Chunk(size + sizeof(uint32));
    uint32 *uint32_ptr = (uint32*)message_chunk->read_ptr();
    *uint32_ptr = htonl(size);
    memcpy(message_chunk->read_ptr() + sizeof(uint32), data, size);
    message_chunk->write_ptr(size + sizeof(uint32));
    m_send_msg_queue.push(message_chunk);
    m_poller_task->write_connection(shared_from_this());
}

void Connection::close()
{
    m_poller_task->close_connection(shared_from_this());
}

const Addr& Connection::peer_addr()
{
    return m_peer_addr;
}

void Connection::parse()
{
    while(true)
    {
        char *msg_length_buf = (char*)(&m_cur_msg_length);
        uint32 remain_bytes = sizeof(uint32);

        if(m_cur_msg_length != 0)
        {
            goto after_parse_length;
        }

        if(m_recv_msg_queue.length() < sizeof(uint32))
        {
            break;
        }

        while(auto *message_chunk = m_recv_msg_queue.pop())
        {
            uint32 length = message_chunk->length();

            if(length < remain_bytes)
            {
                memcpy(msg_length_buf + sizeof(uint32) - remain_bytes, message_chunk->read_ptr(), length);
                remain_bytes -= length;
                delete message_chunk;
            }
            else
            {
                memcpy(msg_length_buf + sizeof(uint32) - remain_bytes, message_chunk->read_ptr(), remain_bytes);

                if(length == remain_bytes)
                {
                    delete message_chunk;
                }
                else
                {
                    message_chunk->read_ptr(remain_bytes);
                    m_recv_msg_queue.push_front(message_chunk);
                }

                break;
            }
        }

        m_cur_msg_length = ntohl(m_cur_msg_length);

    after_parse_length:
        if(m_recv_msg_queue.length() < m_cur_msg_length)
        {
            break;
        }

        const uint32 MAX_MSG_LEN = 102400;
        char data[MAX_MSG_LEN] = {0};
        remain_bytes = m_cur_msg_length;

        if(m_cur_msg_length > MAX_MSG_LEN / 2)
        {
            LOG_ERROR("message length exceed half of MAX_MSG_LEN(%d)", MAX_MSG_LEN);

            if(m_cur_msg_length > MAX_MSG_LEN)
            {
                LOG_FATAL("message length exceed MAX_MSG_LEN(%d)", MAX_MSG_LEN);

                break;
            }
        }

        while(auto *message_chunk = m_recv_msg_queue.pop())
        {
            uint32 length = message_chunk->length();

            if(length < remain_bytes)
            {
                memcpy(data + m_cur_msg_length - remain_bytes, message_chunk->read_ptr(), length);
                remain_bytes -= length;
                delete message_chunk;
            }
            else
            {
                memcpy(data + m_cur_msg_length - remain_bytes, message_chunk->read_ptr(), remain_bytes);

                if(length == remain_bytes)
                {
                    delete message_chunk;
                }
                else
                {
                    message_chunk->read_ptr(remain_bytes);
                    m_recv_msg_queue.push_front(message_chunk);
                }

                std::unique_ptr<Message> message(new Message(shared_from_this()));
                message->m_raw_data.assign(data, m_cur_msg_length);
                m_cur_msg_length = 0;
                rapidjson::Document &doc = message->doc();
                doc.Parse(message->m_raw_data.c_str());

                if(!doc.HasParseError())
                {
                    if(!doc.HasMember("msg_type"))
                    {
                        break;
                    }

                    const rapidjson::Value &msg_type = doc["msg_type"];

                    if(!msg_type.IsUint())
                    {
                        break;
                    }

                    message->m_type = msg_type.GetUint();

                    if(!doc.HasMember("msg_cmd"))
                    {
                        break;
                    }

                    const rapidjson::Value &msg_cmd = doc["msg_cmd"];

                    if(!msg_cmd.IsUint())
                    {
                        break;
                    }

                    message->m_cmd = msg_cmd.GetUint();
                    m_dispatch_cb(std::move(message));
                }

                break;
            }
        }
    }
}

}
}
<commit_msg>optimize msg buf when parse<commit_after>/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                    _______    _                                     *
 *                   (  ____ \  ( \     |\     /|                      *
 *                   | (    \/  | (     ( \   / )                      *
 *                   | (__      | |      \ (_) /                       *
 *                   |  __)     | |       \   /                        *
 *                   | (        | |        ) (                         *
 *                   | )        | (____/\  | |                         *
 *                   |/         (_______/  \_/                         *
 *                                                                     *
 *                                                                     *
 *     fly is an awesome c++11 network library.                        *
 *                                                                     *
 *   @author: lichuan                                                  *
 *   @qq: 308831759                                                    *
 *   @email: 308831759@qq.com                                          *
 *   @github: https://github.com/lichuan/fly                           *
 *   @date: 2015-06-22 18:22:00                                        *
 *                                                                     *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <unistd.h>
#include <cstring>
#include <netinet/in.h>
#include "fly/net/connection.hpp"
#include "fly/net/poller_task.hpp"
#include "fly/base/logger.hpp"

namespace fly {
namespace net {

fly::base::ID_Allocator Connection::m_id_allocator;

Connection::~Connection()
{
    while(auto *message_chunk = m_recv_msg_queue.pop())
    {
        delete message_chunk;
    }

    while(auto *message_chunk = m_send_msg_queue.pop())
    {
        delete message_chunk;
    }
}

Connection::Connection(int32 fd, const Addr &peer_addr)
{
    m_fd = fd;
    m_peer_addr = peer_addr;
}

uint64 Connection::id()
{
    return m_id;
}

void Connection::send(rapidjson::Document &doc)
{
    rapidjson::StringBuffer buffer;
    rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
    doc.Accept(writer);
    send(buffer.GetString(), buffer.GetSize());
}

void Connection::send(const void *data, uint32 size)
{
    Message_Chunk *message_chunk = new Message_Chunk(size + sizeof(uint32));
    uint32 *uint32_ptr = (uint32*)message_chunk->read_ptr();
    *uint32_ptr = htonl(size);
    memcpy(message_chunk->read_ptr() + sizeof(uint32), data, size);
    message_chunk->write_ptr(size + sizeof(uint32));
    m_send_msg_queue.push(message_chunk);
    m_poller_task->write_connection(shared_from_this());
}

void Connection::close()
{
    m_poller_task->close_connection(shared_from_this());
}

const Addr& Connection::peer_addr()
{
    return m_peer_addr;
}

void Connection::parse()
{
    while(true)
    {
        char *msg_length_buf = (char*)(&m_cur_msg_length);
        uint32 remain_bytes = sizeof(uint32);

        if(m_cur_msg_length != 0)
        {
            goto after_parse_length;
        }

        if(m_recv_msg_queue.length() < sizeof(uint32))
        {
            break;
        }

        while(auto *message_chunk = m_recv_msg_queue.pop())
        {
            uint32 length = message_chunk->length();

            if(length < remain_bytes)
            {
                memcpy(msg_length_buf + sizeof(uint32) - remain_bytes, message_chunk->read_ptr(), length);
                remain_bytes -= length;
                delete message_chunk;
            }
            else
            {
                memcpy(msg_length_buf + sizeof(uint32) - remain_bytes, message_chunk->read_ptr(), remain_bytes);

                if(length == remain_bytes)
                {
                    delete message_chunk;
                }
                else
                {
                    message_chunk->read_ptr(remain_bytes);
                    m_recv_msg_queue.push_front(message_chunk);
                }

                break;
            }
        }

        m_cur_msg_length = ntohl(m_cur_msg_length);

    after_parse_length:
        if(m_recv_msg_queue.length() < m_cur_msg_length)
        {
            break;
        }

        const uint32 MAX_MSG_LEN = 102400;
        char msg_buf[MAX_MSG_LEN] = {0};
        char *data = msg_buf;
        bool is_new_buf = false;
        remain_bytes = m_cur_msg_length;

        if(m_cur_msg_length > MAX_MSG_LEN)
        {
            LOG_ERROR("message length exceed MAX_MSG_LEN(%d)", MAX_MSG_LEN);
            data = new char[m_cur_msg_length];
            is_new_buf = true;
        }
        else if(m_cur_msg_length > MAX_MSG_LEN / 2)
        {
            LOG_ERROR("message length exceed half of MAX_MSG_LEN(%d)", MAX_MSG_LEN);
        }

        while(auto *message_chunk = m_recv_msg_queue.pop())
        {
            uint32 length = message_chunk->length();

            if(length < remain_bytes)
            {
                memcpy(data + m_cur_msg_length - remain_bytes, message_chunk->read_ptr(), length);
                remain_bytes -= length;
                delete message_chunk;
            }
            else
            {
                memcpy(data + m_cur_msg_length - remain_bytes, message_chunk->read_ptr(), remain_bytes);

                if(length == remain_bytes)
                {
                    delete message_chunk;
                }
                else
                {
                    message_chunk->read_ptr(remain_bytes);
                    m_recv_msg_queue.push_front(message_chunk);
                }

                std::unique_ptr<Message> message(new Message(shared_from_this()));
                message->m_raw_data.assign(data, m_cur_msg_length);
                m_cur_msg_length = 0;

                if(is_new_buf)
                {
                    delete[] data;
                }

                rapidjson::Document &doc = message->doc();
                doc.Parse(message->m_raw_data.c_str());

                if(!doc.HasParseError())
                {
                    if(!doc.HasMember("msg_type"))
                    {
                        break;
                    }

                    const rapidjson::Value &msg_type = doc["msg_type"];

                    if(!msg_type.IsUint())
                    {
                        break;
                    }

                    message->m_type = msg_type.GetUint();

                    if(!doc.HasMember("msg_cmd"))
                    {
                        break;
                    }

                    const rapidjson::Value &msg_cmd = doc["msg_cmd"];

                    if(!msg_cmd.IsUint())
                    {
                        break;
                    }

                    message->m_cmd = msg_cmd.GetUint();
                    m_dispatch_cb(std::move(message));
                }

                break;
            }
        }
    }
}

}
}
<|endoftext|>


<commit_before>/* Copyright 2013 Yahoo! Inc.                                         */
/* See LICENSE in the root of the distribution for licensing details. */

// Unit Tests for mdbm_import and mdbm_export
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <limits.h>

#include <iostream>
#include <vector>
#include <algorithm>

#include <cppunit/TestAssert.h>
#include <cppunit/TestCase.h>
#include <cppunit/TestFixture.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>

#include "mdbm.h"
#include "TestBase.hh"

// Include mdbm_import and redefine main() and usage() to perform unit tests w/o using fork+exec

#define main imp_main_wrapper
#define usage imp_usage_wrapper
#include "../../tools/mdbm_import.cc"
#undef main
#undef usage

// Include mdbm_export and redefine main() and usage() to perform unit tests w/o using fork+exec

#define main exp_main_wrapper
#define usage exp_usage_wrapper
#include "../../tools/mdbm_export.c"
#undef main
#undef usage

class ImportTestBase : public CppUnit::TestFixture, public TestBase
{

public:
    ImportTestBase(int vFlag) : TestBase(vFlag, "Import Test Suite") {}
    void setUp();
    void tearDown() { }

    // unit tests in this suite
    void DoExport();
    void DoExportWithLocking(const string &lockmode);
    void TestImportWithDelete();
    void TestImportNoDelete();
    void TestSmallDbPageSize();

protected:
  static string file1;
  static string file2;
  static string exfile;
  void MakeTestMdbm(const string& fname);
  void ResetGetOpt();
  void TestValues(const string& fname, bool doDelete);
  string CreateCdbFile(int count = 100, int offst = 50);
  void VerifyMdbmFile(const string &fname, int count = 100, int offst = 50, int lflags=0);
  //void TestWithLockingMode(const string &infile, const string &lockmode, int lflags=0);
  void TestImportWithLockingMode(const string &infile, const string &lockmode, int count = 100);
};

string ImportTestBase::file1;
string ImportTestBase::file2;
string ImportTestBase::exfile;

void ImportTestBase::setUp()
{
    if (file1=="") {
      file1 = GetTmpName("input");
      file2 = GetTmpName("output");
      exfile = GetTmpName("export");
      MakeTestMdbm(file1);
    }
}

void ImportTestBase::MakeTestMdbm(const string& fname) {
    int i;
    char key[4096];
    char val[4096];
    datum kdat, vdat;
    int flags = MDBM_O_RDWR | MDBM_O_CREAT | MDBM_O_TRUNC | versionFlag;
    MdbmHolder db = mdbm_open(fname.c_str(), flags, 0644, 1024, 4096);

    kdat.dptr = key;
    vdat.dptr = val;
    // first db only
    for (i=0; i<5; ++i) { // store regular values
      kdat.dsize = snprintf(key, sizeof(key), "key %04d -.val", i);
      vdat.dsize = snprintf(val, sizeof(val), "whereas this is value %07d", i);
      mdbm_store(db, kdat, vdat, MDBM_REPLACE);
    }
    for (i=5; i<10; ++i) { // store zero-length values
      kdat.dsize = snprintf(key, sizeof(key), "key %04d .-val", i);
      val[0] = 0;
      vdat.dsize = 0;
      mdbm_store(db, kdat, vdat, MDBM_REPLACE);
    }
}

void ImportTestBase::ResetGetOpt() {
    reset_getopt();
}

void ImportTestBase::TestValues(const string& fname, bool doDelete) {
    // test values
    int i;
    char key[4096];
    char val[4096];
    datum kdat, vdat, fvdat;

    int flags = MDBM_O_RDONLY;
    MdbmHolder db = mdbm_open(fname.c_str(), flags|MDBM_ANY_LOCKS, 0644, 1024, 4096);

    kdat.dptr = key;
    vdat.dptr = val;
    // first db only
    for (i=0; i<5; ++i) { // store regular values
      kdat.dsize = snprintf(key, sizeof(key), "key %04d -.val", i);
      vdat.dsize = snprintf(val, sizeof(val), "whereas this is value %07d", i);
      fvdat = mdbm_fetch(db, kdat);
      CPPUNIT_ASSERT(fvdat.dsize == vdat.dsize);
      CPPUNIT_ASSERT(fvdat.dptr != NULL);
      CPPUNIT_ASSERT(0 == memcmp(fvdat.dptr, vdat.dptr, vdat.dsize));
    }
    for (i=5; i<10; ++i) { // store zero-length values
      kdat.dsize = snprintf(key, sizeof(key), "key %04d .-val", i);
      val[0] = 0;
      vdat.dsize = 0;
      errno = 0;
      fvdat = mdbm_fetch(db, kdat);
      if (doDelete) {
        CPPUNIT_ASSERT(fvdat.dptr == NULL);
        CPPUNIT_ASSERT(errno == ENOENT);
      } else {
        CPPUNIT_ASSERT(fvdat.dptr != NULL);
        CPPUNIT_ASSERT(errno == 0);
      }
      CPPUNIT_ASSERT(fvdat.dsize == 0);
    }
}

string
ImportTestBase::CreateCdbFile(int count, int offst)
{
    string fname = GetTmpName("cdbinput");

    FILE *fptr = fopen(fname.c_str(), "w");

    CPPUNIT_ASSERT(fptr != NULL);

    string key, val;
    for (int i = 0; i < count; ++i) {
       key = string("key") + ToStr(i);
       val = string("value") + ToStr(i + offst);
       fprintf(fptr, "+%d,%d:%s->%s\n", (int)key.size(), (int)val.size(), key.c_str(), val.c_str());
    }
    fprintf(fptr, "\n");
    fclose(fptr);

    return fname;
}

void
ImportTestBase::VerifyMdbmFile(const string &fname, int count, int offst, int lflags)
{
    MdbmHolder db = mdbm_open(fname.c_str(), MDBM_O_RDONLY|lflags, 0444, 0, 0);
    CPPUNIT_ASSERT(NULL != (MDBM *)db);
    string key, val;
    errno = 0;
    for (int i = 0; i < count; ++i) {
        key = string("key") + ToStr(i);
        val = string("value") + ToStr(i + offst);
        const datum kdat = {(char *)key.c_str(), (int)key.size()};
        datum fetch = mdbm_fetch(db, kdat);
        CPPUNIT_ASSERT(fetch.dptr != NULL);
        int len = static_cast<int>(val.size());
        CPPUNIT_ASSERT_EQUAL(len, fetch.dsize);
        CPPUNIT_ASSERT_EQUAL(0, memcmp(fetch.dptr, val.c_str(), val.size()));
    }
}

void ImportTestBase::DoExport()
{
    TRACE_TEST_CASE("DoExport");
    { // export
      const char* args[] = { "foo", "-c", "-o", exfile.c_str(), file1.c_str(), NULL };
      ResetGetOpt();
      int ret = exp_main_wrapper(sizeof(args)/sizeof(args[0])-1, (char**)args);
      CPPUNIT_ASSERT(ret == 0);
      TestValues(file1, false);
    }
}

void ImportTestBase::DoExportWithLocking(const string &lockmode) {
    TRACE_TEST_CASE(lockmode + "-DoExportWithLocking");
    { // export
      mdbm_delete_lockfiles(file1.c_str());
      const char* args[] = { "foo", "-c", "-o", exfile.c_str(), file1.c_str(),
                             "-L", lockmode.c_str(), NULL };
      ResetGetOpt();
      int ret = exp_main_wrapper(sizeof(args)/sizeof(args[0])-1, (char**)args);
      CPPUNIT_ASSERT(ret == 0);
      TestValues(file1, false);
    }
}

void ImportTestBase::TestImportWithDelete()
{
    TRACE_TEST_CASE("TestImportWithDelete");
    string cmd = "rm -f " + file2;
    system(cmd.c_str());

    { // import, with delete zero-length values
      const char* args[] = { "foo", "-c", "-S", "1", "-D", "-i", exfile.c_str(), file2.c_str(), NULL };
      ResetGetOpt();
      int ret = imp_main_wrapper(sizeof(args)/sizeof(args[0])-1, (char**)args);
      fprintf(stderr, "import return code is %d\n", ret);
      CPPUNIT_ASSERT(ret == 0);
      TestValues(file2, true);
    }
}

void ImportTestBase::TestImportNoDelete()
{
    TRACE_TEST_CASE("TestImportNoDelete");
    string cmd = "rm -f " + file2;
    system(cmd.c_str());

    { // import, but *don't* delete zero-length values
      const char* args[] = { "foo", "-c", "-S", "1", "-i", exfile.c_str(), file2.c_str(), NULL };
      //const char* args[] = { "foo", "-c", "-i", exfile.c_str(), file2.c_str(), NULL };
      ResetGetOpt();
      int ret = imp_main_wrapper(sizeof(args)/sizeof(args[0])-1, (char**)args);
      fprintf(stderr, "import return code is %d\n", ret);
      CPPUNIT_ASSERT(ret == 0);
      TestValues(file2, false);
    }
}

void
ImportTestBase::TestSmallDbPageSize()
{
    string infile(CreateCdbFile());
    string outfile = GetTmpName("out1pg");
    const char* args[] = { "foo", "-c", "-p", "1024",
                           "-i", infile.c_str(), outfile.c_str(), NULL };
    ResetGetOpt();
    int ret = imp_main_wrapper(sizeof(args)/sizeof(args[0])-1, (char**)args);
    CPPUNIT_ASSERT(ret == 0);
    VerifyMdbmFile(outfile);

}

// Will generate the following warning to stdout when using partition locking:
//   Partition locking requires a fixed size DB.
//   Consider using the -d or -y options to specify the size
void
ImportTestBase::TestImportWithLockingMode(const string &infile, const string &lockmode, int count)
{
    TRACE_TEST_CASE(lockmode + "-TestImportWithLockingMode");
    string outfile = GetTmpName("out" + lockmode);
    const char* args[] = { "foo", "-c", "-L", lockmode.c_str(),
                           "-i", infile.c_str(), outfile.c_str(), NULL };
    ResetGetOpt();
fprintf(stderr, "TESTWITHLOCKINGMODE %s\n", outfile.c_str());
    int ret = imp_main_wrapper(sizeof(args)/sizeof(args[0])-1, (char**)args);
    CPPUNIT_ASSERT(ret == 0);
    //VerifyMdbmFile(outfile, 100, 50, lflags);
    VerifyMdbmFile(outfile, 100, 50, MDBM_ANY_LOCKS);
}

class ImportTestV3 : public ImportTestBase
{
    CPPUNIT_TEST_SUITE(ImportTestV3);
    CPPUNIT_TEST(DoExport);
    CPPUNIT_TEST(TestImportWithDelete);
    CPPUNIT_TEST(TestImportNoDelete);
    CPPUNIT_TEST(TestSmallDbPageSize);
    CPPUNIT_TEST(TestAllLocking);
    CPPUNIT_TEST_SUITE_END();

public:
    ImportTestV3() : ImportTestBase(MDBM_CREATE_V3) {}
    void TestAllLocking();
};

CPPUNIT_TEST_SUITE_REGISTRATION(ImportTestV3);

void
ImportTestV3::TestAllLocking()
{
    string infilename(CreateCdbFile());
    TestImportWithLockingMode(infilename, string("exclusive"));
    TestImportWithLockingMode(infilename, string("partition"));
    TestImportWithLockingMode(infilename, string("shared"));
    TestImportWithLockingMode(infilename, string("none"));

    DoExportWithLocking(string("exclusive"));
    DoExportWithLocking(string("partition"));
    DoExportWithLocking(string("shared"));
    DoExportWithLocking(string("none"));
}

<commit_msg>reorder getopt args for compatibility with NetBSD getopt<commit_after>/* Copyright 2013 Yahoo! Inc.                                         */
/* See LICENSE in the root of the distribution for licensing details. */

// Unit Tests for mdbm_import and mdbm_export
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <limits.h>

#include <iostream>
#include <vector>
#include <algorithm>

#include <cppunit/TestAssert.h>
#include <cppunit/TestCase.h>
#include <cppunit/TestFixture.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>

#include "mdbm.h"
#include "TestBase.hh"

// Include mdbm_import and redefine main() and usage() to perform unit tests w/o using fork+exec

#define main imp_main_wrapper
#define usage imp_usage_wrapper
#include "../../tools/mdbm_import.cc"
#undef main
#undef usage

// Include mdbm_export and redefine main() and usage() to perform unit tests w/o using fork+exec

#define main exp_main_wrapper
#define usage exp_usage_wrapper
#include "../../tools/mdbm_export.c"
#undef main
#undef usage

class ImportTestBase : public CppUnit::TestFixture, public TestBase
{

public:
    ImportTestBase(int vFlag) : TestBase(vFlag, "Import Test Suite") {}
    void setUp();
    void tearDown() { }

    // unit tests in this suite
    void DoExport();
    void DoExportWithLocking(const string &lockmode);
    void TestImportWithDelete();
    void TestImportNoDelete();
    void TestSmallDbPageSize();

protected:
  static string file1;
  static string file2;
  static string exfile;
  void MakeTestMdbm(const string& fname);
  void ResetGetOpt();
  void TestValues(const string& fname, bool doDelete);
  string CreateCdbFile(int count = 100, int offst = 50);
  void VerifyMdbmFile(const string &fname, int count = 100, int offst = 50, int lflags=0);
  //void TestWithLockingMode(const string &infile, const string &lockmode, int lflags=0);
  void TestImportWithLockingMode(const string &infile, const string &lockmode, int count = 100);
};

string ImportTestBase::file1;
string ImportTestBase::file2;
string ImportTestBase::exfile;

void ImportTestBase::setUp()
{
    if (file1=="") {
      file1 = GetTmpName("input");
      file2 = GetTmpName("output");
      exfile = GetTmpName("export");
      MakeTestMdbm(file1);
    }
}

void ImportTestBase::MakeTestMdbm(const string& fname) {
    int i;
    char key[4096];
    char val[4096];
    datum kdat, vdat;
    int flags = MDBM_O_RDWR | MDBM_O_CREAT | MDBM_O_TRUNC | versionFlag;
    MdbmHolder db = mdbm_open(fname.c_str(), flags, 0644, 1024, 4096);

    kdat.dptr = key;
    vdat.dptr = val;
    // first db only
    for (i=0; i<5; ++i) { // store regular values
      kdat.dsize = snprintf(key, sizeof(key), "key %04d -.val", i);
      vdat.dsize = snprintf(val, sizeof(val), "whereas this is value %07d", i);
      mdbm_store(db, kdat, vdat, MDBM_REPLACE);
    }
    for (i=5; i<10; ++i) { // store zero-length values
      kdat.dsize = snprintf(key, sizeof(key), "key %04d .-val", i);
      val[0] = 0;
      vdat.dsize = 0;
      mdbm_store(db, kdat, vdat, MDBM_REPLACE);
    }
}

void ImportTestBase::ResetGetOpt() {
    reset_getopt();
}

void ImportTestBase::TestValues(const string& fname, bool doDelete) {
    // test values
    int i;
    char key[4096];
    char val[4096];
    datum kdat, vdat, fvdat;

    int flags = MDBM_O_RDONLY;
    MdbmHolder db = mdbm_open(fname.c_str(), flags|MDBM_ANY_LOCKS, 0644, 1024, 4096);

    kdat.dptr = key;
    vdat.dptr = val;
    // first db only
    for (i=0; i<5; ++i) { // store regular values
      kdat.dsize = snprintf(key, sizeof(key), "key %04d -.val", i);
      vdat.dsize = snprintf(val, sizeof(val), "whereas this is value %07d", i);
      fvdat = mdbm_fetch(db, kdat);
      CPPUNIT_ASSERT(fvdat.dsize == vdat.dsize);
      CPPUNIT_ASSERT(fvdat.dptr != NULL);
      CPPUNIT_ASSERT(0 == memcmp(fvdat.dptr, vdat.dptr, vdat.dsize));
    }
    for (i=5; i<10; ++i) { // store zero-length values
      kdat.dsize = snprintf(key, sizeof(key), "key %04d .-val", i);
      val[0] = 0;
      vdat.dsize = 0;
      errno = 0;
      fvdat = mdbm_fetch(db, kdat);
      if (doDelete) {
        CPPUNIT_ASSERT(fvdat.dptr == NULL);
        CPPUNIT_ASSERT(errno == ENOENT);
      } else {
        CPPUNIT_ASSERT(fvdat.dptr != NULL);
        CPPUNIT_ASSERT(errno == 0);
      }
      CPPUNIT_ASSERT(fvdat.dsize == 0);
    }
}

string
ImportTestBase::CreateCdbFile(int count, int offst)
{
    string fname = GetTmpName("cdbinput");

    FILE *fptr = fopen(fname.c_str(), "w");

    CPPUNIT_ASSERT(fptr != NULL);

    string key, val;
    for (int i = 0; i < count; ++i) {
       key = string("key") + ToStr(i);
       val = string("value") + ToStr(i + offst);
       fprintf(fptr, "+%d,%d:%s->%s\n", (int)key.size(), (int)val.size(), key.c_str(), val.c_str());
    }
    fprintf(fptr, "\n");
    fclose(fptr);

    return fname;
}

void
ImportTestBase::VerifyMdbmFile(const string &fname, int count, int offst, int lflags)
{
    MdbmHolder db = mdbm_open(fname.c_str(), MDBM_O_RDONLY|lflags, 0444, 0, 0);
    CPPUNIT_ASSERT(NULL != (MDBM *)db);
    string key, val;
    errno = 0;
    for (int i = 0; i < count; ++i) {
        key = string("key") + ToStr(i);
        val = string("value") + ToStr(i + offst);
        const datum kdat = {(char *)key.c_str(), (int)key.size()};
        datum fetch = mdbm_fetch(db, kdat);
        CPPUNIT_ASSERT(fetch.dptr != NULL);
        int len = static_cast<int>(val.size());
        CPPUNIT_ASSERT_EQUAL(len, fetch.dsize);
        CPPUNIT_ASSERT_EQUAL(0, memcmp(fetch.dptr, val.c_str(), val.size()));
    }
}

void ImportTestBase::DoExport()
{
    TRACE_TEST_CASE("DoExport");
    { // export
      const char* args[] = { "foo", "-c", "-o", exfile.c_str(), file1.c_str(), NULL };
      ResetGetOpt();
      int ret = exp_main_wrapper(sizeof(args)/sizeof(args[0])-1, (char**)args);
      CPPUNIT_ASSERT(ret == 0);
      TestValues(file1, false);
    }
}

void ImportTestBase::DoExportWithLocking(const string &lockmode) {
    TRACE_TEST_CASE(lockmode + "-DoExportWithLocking");
    { // export
      mdbm_delete_lockfiles(file1.c_str());
      const char* args[] = { "foo", "-c", "-o", exfile.c_str(),
                             "-L", lockmode.c_str(), file1.c_str(), NULL };
      ResetGetOpt();
      int ret = exp_main_wrapper(sizeof(args)/sizeof(args[0])-1, (char**)args);
      CPPUNIT_ASSERT(ret == 0);
      TestValues(file1, false);
    }
}

void ImportTestBase::TestImportWithDelete()
{
    TRACE_TEST_CASE("TestImportWithDelete");
    string cmd = "rm -f " + file2;
    system(cmd.c_str());

    { // import, with delete zero-length values
      const char* args[] = { "foo", "-c", "-S", "1", "-D", "-i", exfile.c_str(), file2.c_str(), NULL };
      ResetGetOpt();
      int ret = imp_main_wrapper(sizeof(args)/sizeof(args[0])-1, (char**)args);
      fprintf(stderr, "import return code is %d\n", ret);
      CPPUNIT_ASSERT(ret == 0);
      TestValues(file2, true);
    }
}

void ImportTestBase::TestImportNoDelete()
{
    TRACE_TEST_CASE("TestImportNoDelete");
    string cmd = "rm -f " + file2;
    system(cmd.c_str());

    { // import, but *don't* delete zero-length values
      const char* args[] = { "foo", "-c", "-S", "1", "-i", exfile.c_str(), file2.c_str(), NULL };
      //const char* args[] = { "foo", "-c", "-i", exfile.c_str(), file2.c_str(), NULL };
      ResetGetOpt();
      int ret = imp_main_wrapper(sizeof(args)/sizeof(args[0])-1, (char**)args);
      fprintf(stderr, "import return code is %d\n", ret);
      CPPUNIT_ASSERT(ret == 0);
      TestValues(file2, false);
    }
}

void
ImportTestBase::TestSmallDbPageSize()
{
    string infile(CreateCdbFile());
    string outfile = GetTmpName("out1pg");
    const char* args[] = { "foo", "-c", "-p", "1024",
                           "-i", infile.c_str(), outfile.c_str(), NULL };
    ResetGetOpt();
    int ret = imp_main_wrapper(sizeof(args)/sizeof(args[0])-1, (char**)args);
    CPPUNIT_ASSERT(ret == 0);
    VerifyMdbmFile(outfile);

}

// Will generate the following warning to stdout when using partition locking:
//   Partition locking requires a fixed size DB.
//   Consider using the -d or -y options to specify the size
void
ImportTestBase::TestImportWithLockingMode(const string &infile, const string &lockmode, int count)
{
    TRACE_TEST_CASE(lockmode + "-TestImportWithLockingMode");
    string outfile = GetTmpName("out" + lockmode);
    const char* args[] = { "foo", "-c", "-L", lockmode.c_str(),
                           "-i", infile.c_str(), outfile.c_str(), NULL };
    ResetGetOpt();
fprintf(stderr, "TESTWITHLOCKINGMODE %s\n", outfile.c_str());
    int ret = imp_main_wrapper(sizeof(args)/sizeof(args[0])-1, (char**)args);
    CPPUNIT_ASSERT(ret == 0);
    //VerifyMdbmFile(outfile, 100, 50, lflags);
    VerifyMdbmFile(outfile, 100, 50, MDBM_ANY_LOCKS);
}

class ImportTestV3 : public ImportTestBase
{
    CPPUNIT_TEST_SUITE(ImportTestV3);
    CPPUNIT_TEST(DoExport);
    CPPUNIT_TEST(TestImportWithDelete);
    CPPUNIT_TEST(TestImportNoDelete);
    CPPUNIT_TEST(TestSmallDbPageSize);
    CPPUNIT_TEST(TestAllLocking);
    CPPUNIT_TEST_SUITE_END();

public:
    ImportTestV3() : ImportTestBase(MDBM_CREATE_V3) {}
    void TestAllLocking();
};

CPPUNIT_TEST_SUITE_REGISTRATION(ImportTestV3);

void
ImportTestV3::TestAllLocking()
{
    string infilename(CreateCdbFile());
    TestImportWithLockingMode(infilename, string("exclusive"));
    TestImportWithLockingMode(infilename, string("partition"));
    TestImportWithLockingMode(infilename, string("shared"));
    TestImportWithLockingMode(infilename, string("none"));

    DoExportWithLocking(string("exclusive"));
    DoExportWithLocking(string("partition"));
    DoExportWithLocking(string("shared"));
    DoExportWithLocking(string("none"));
}

<|endoftext|>


<commit_before>/*
 * Copyright (C) 2016 Jan Grulich
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "gnomehintssettings.h"

#include <QDir>
#include <QString>
#include <QPalette>
#include <QMainWindow>
#include <QApplication>
#include <QGuiApplication>
#include <QDialogButtonBox>
#include <QToolBar>
#include <QLoggingCategory>

#include <gtk-3.0/gtk/gtksettings.h>

Q_LOGGING_CATEGORY(QGnomePlatform, "qt.qpa.qgnomeplatform")

GnomeHintsSettings::GnomeHintsSettings()
    : QObject(0)
    , m_gtkThemeDarkVariant(false)
    , m_palette(nullptr)
    , m_settings(g_settings_new("org.gnome.desktop.interface"))
{
    gtk_init(nullptr, nullptr);

    // Get current theme and variant
    // g_object_get(gtk_settings_get_default(), "gtk-theme-name", &m_gtkTheme, NULL);
    m_gtkTheme = g_settings_get_string(m_settings, "gtk-theme");
    g_object_get(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", &m_gtkThemeDarkVariant, NULL);

    if (!m_gtkTheme) {
        qCWarning(QGnomePlatform) << "Couldn't get current gtk theme!";
    } else {
        qCDebug(QGnomePlatform) << "Theme name: " << m_gtkTheme;
        qCDebug(QGnomePlatform) << "Dark version: " << (m_gtkThemeDarkVariant ? "yes" : "no");
    }

    gint cursorBlinkTime = g_settings_get_int(m_settings, "cursor-blink-time");
//     g_object_get(gtk_settings_get_default(), "gtk-cursor-blink-time", &cursorBlinkTime, NULL);
    if (cursorBlinkTime >= 100) {
        qCDebug(QGnomePlatform) << "Cursor blink time: " << cursorBlinkTime;
        m_hints[QPlatformTheme::CursorFlashTime] = cursorBlinkTime;
    } else {
        m_hints[QPlatformTheme::CursorFlashTime] = 1200;
    }

    gint doubleClickTime = 400;
    g_object_get(gtk_settings_get_default(), "gtk-double-click-time", &doubleClickTime, NULL);
    qCDebug(QGnomePlatform) << "Double click time: " << doubleClickTime;
    m_hints[QPlatformTheme::MouseDoubleClickInterval] = doubleClickTime;

    guint longPressTime = 500;
    g_object_get(gtk_settings_get_default(), "gtk-long-press-time", &longPressTime, NULL);
    qCDebug(QGnomePlatform) << "Long press time: " << longPressTime;
    m_hints[QPlatformTheme::MousePressAndHoldInterval] = longPressTime;

    gint doubleClickDistance = 5;
    g_object_get(gtk_settings_get_default(), "gtk-double-click-distance", &doubleClickDistance, NULL);
    qCDebug(QGnomePlatform) << "Double click distance: " << doubleClickDistance;
    m_hints[QPlatformTheme::MouseDoubleClickDistance] = doubleClickDistance;

    gint startDragDistance = 8;
    g_object_get(gtk_settings_get_default(), "gtk-dnd-drag-threshold", &startDragDistance, NULL);
    qCDebug(QGnomePlatform) << "Dnd drag threshold: " << startDragDistance;
    m_hints[QPlatformTheme::StartDragDistance] = startDragDistance;

    guint passwordMaskDelay = 0;
    g_object_get(gtk_settings_get_default(), "gtk-entry-password-hint-timeout", &passwordMaskDelay, NULL);
    qCDebug(QGnomePlatform) << "Password hint timeout: " << passwordMaskDelay;
    m_hints[QPlatformTheme::PasswordMaskDelay] = passwordMaskDelay;

    gchar *systemIconTheme = g_settings_get_string(m_settings, "icon-theme");
//     g_object_get(gtk_settings_get_default(), "gtk-icon-theme-name", &systemIconTheme, NULL);
    if (systemIconTheme) {
        qCDebug(QGnomePlatform) << "Icon theme: " << systemIconTheme;
        m_hints[QPlatformTheme::SystemIconThemeName] = systemIconTheme;
        free(systemIconTheme);
    } else {
        m_hints[QPlatformTheme::SystemIconThemeName] = "Adwaita";
    }
    m_hints[QPlatformTheme::SystemIconFallbackThemeName] = "Adwaita";
    m_hints[QPlatformTheme::IconThemeSearchPaths] = xdgIconThemePaths();

    QStringList styleNames;
    styleNames << QStringLiteral("adwaita")
               << QStringLiteral("gtk+")
               << QStringLiteral("fusion")
               << QStringLiteral("windows");
    m_hints[QPlatformTheme::StyleNames] = styleNames;

    m_hints[QPlatformTheme::DialogButtonBoxLayout] = QDialogButtonBox::GnomeLayout;
    m_hints[QPlatformTheme::DialogButtonBoxButtonsHaveIcons] = true;
    m_hints[QPlatformTheme::KeyboardScheme] = QPlatformTheme::GnomeKeyboardScheme;
    m_hints[QPlatformTheme::IconPixmapSizes] = QVariant::fromValue(QList<int>() << 512 << 256 << 128 << 64 << 32 << 22 << 16 << 8);
    m_hints[QPlatformTheme::PasswordMaskCharacter] = QVariant(QChar(0x2022));

    // Watch for changes
    g_signal_connect(m_settings, "changed::gtk-theme", G_CALLBACK(gsettingPropertyChanged), this);
    g_signal_connect(m_settings, "changed::icon-theme", G_CALLBACK(gsettingPropertyChanged), this);
    g_signal_connect(m_settings, "changed::cursor-blink-time", G_CALLBACK(gsettingPropertyChanged), this);
    g_signal_connect(m_settings, "changed::font-name", G_CALLBACK(gsettingPropertyChanged), this);
    g_signal_connect(m_settings, "changed::monospace-font-name", G_CALLBACK(gsettingPropertyChanged), this);
    g_signal_connect(m_settings, "changed::text-scaling-factor", G_CALLBACK(gsettingPropertyChanged), this);

    // g_signal_connect(gtk_settings_get_default(), "notify::gtk-theme-name", G_CALLBACK(gtkThemeChanged), this);

                                        /* Other theme hints */
    // KeyboardInputInterval, StartDragTime, KeyboardAutoRepeatRate, StartDragVelocity, DropShadow,
    // MaximumScrollBarDragDistance, ItemViewActivateItemOnSingleClick, WindowAutoPlacement, DialogButtonBoxButtonsHaveIcons
    // UseFullScreenForPopupMenu, UiEffects, SpellCheckUnderlineStyle, TabFocusBehavior, TabAllWidgets, PasswordMaskCharacter
    // DialogSnapToDefaultButton, ContextMenuOnMouseRelease, WheelScrollLines
    //  TODO TextCursorWidth, ToolButtonStyle, ToolBarIconSize

    // Load fonts
    loadFonts();

    // Load palette
    loadPalette();
}

GnomeHintsSettings::~GnomeHintsSettings()
{
    qDeleteAll(m_fonts);
    delete m_palette;
}

void GnomeHintsSettings::gsettingPropertyChanged(GSettings *settings, gchar *key, GnomeHintsSettings *gnomeHintsSettings)
{
    Q_UNUSED(settings);

    const QString changedProperty = key;

    if (changedProperty == QLatin1String("gtk-theme")) {
        gnomeHintsSettings->themeChanged();
    } else if (changedProperty == QLatin1String("icon-theme")) {
        gnomeHintsSettings->iconsChanged();
    } else if (changedProperty == QLatin1String("cursor-blink-time")) {
        gnomeHintsSettings->cursorBlinkTimeChanged();
    } else if (changedProperty == QLatin1String("font-name")) {
        gnomeHintsSettings->fontChanged();
    } else if (changedProperty == QLatin1String("monospace-font-name")) {
        gnomeHintsSettings->fontChanged();
    } else if (changedProperty == QLatin1String("text-scaling-factor")) {
        gnomeHintsSettings->fontChanged();
    } else {
        qCDebug(QGnomePlatform) << "GSetting property change: " << key;
    }
}

void GnomeHintsSettings::cursorBlinkTimeChanged()
{
    gint cursorBlinkTime = g_settings_get_int(m_settings, "cursor-blink-time");
    if (cursorBlinkTime >= 100) {
        qCDebug(QGnomePlatform) << "Cursor blink time changed to: " << cursorBlinkTime;
        m_hints[QPlatformTheme::CursorFlashTime] = cursorBlinkTime;
    } else {
        qCDebug(QGnomePlatform) << "Cursor blink time changed to: 1200";
        m_hints[QPlatformTheme::CursorFlashTime] = 1200;
    }

    //If we are not a QApplication, means that we are a QGuiApplication, then we do nothing.
    if (!qobject_cast<QApplication *>(QCoreApplication::instance())) {
        return;
    }

    QWidgetList widgets = QApplication::allWidgets();
    Q_FOREACH (QWidget *widget, widgets) {
        if (qobject_cast<QToolBar *>(widget) || qobject_cast<QMainWindow *>(widget)) {
            QEvent event(QEvent::StyleChange);
            QApplication::sendEvent(widget, &event);
        }
    }
}

void GnomeHintsSettings::fontChanged()
{
    loadFonts();

    if (qobject_cast<QApplication *>(QCoreApplication::instance())) {
        QApplication::setFont(*m_fonts[QPlatformTheme::SystemFont]);
        QWidgetList widgets = QApplication::allWidgets();
        Q_FOREACH (QWidget *widget, widgets) {
            widget->setFont(*m_fonts[QPlatformTheme::SystemFont]);
        }
    } else {
        QGuiApplication::setFont(*m_fonts[QPlatformTheme::SystemFont]);
    }
}

void GnomeHintsSettings::iconsChanged()
{
    gchar *systemIconTheme = g_settings_get_string(m_settings, "icon-theme");
    if (systemIconTheme) {
        qCDebug(QGnomePlatform) << "Icon theme changed to: " << systemIconTheme;
        m_hints[QPlatformTheme::SystemIconThemeName] = systemIconTheme;
        free(systemIconTheme);
    } else {
        qCDebug(QGnomePlatform) << "Icon theme changed to: Adwaita";
        m_hints[QPlatformTheme::SystemIconThemeName] = "Adwaita";
    }

    //If we are not a QApplication, means that we are a QGuiApplication, then we do nothing.
    if (!qobject_cast<QApplication *>(QCoreApplication::instance())) {
        return;
    }

    QWidgetList widgets = QApplication::allWidgets();
    Q_FOREACH (QWidget *widget, widgets) {
        if (qobject_cast<QToolBar *>(widget) || qobject_cast<QMainWindow *>(widget)) {
            QEvent event(QEvent::StyleChange);
            QApplication::sendEvent(widget, &event);
        }
    }
}

void GnomeHintsSettings::themeChanged()
{
    loadPalette();

    // QApplication::setPalette and QGuiApplication::setPalette are different functions
    // and non virtual. Call the correct one
    if (qobject_cast<QApplication *>(QCoreApplication::instance())) {
        QApplication::setPalette(*m_palette);
    } else if (qobject_cast<QGuiApplication *>(QCoreApplication::instance())) {
        QGuiApplication::setPalette(*m_palette);
    }
}

void GnomeHintsSettings::loadFonts()
{
    gdouble scaling = g_settings_get_double(m_settings, "text-scaling-factor");
    qCDebug(QGnomePlatform) << "Font scaling: " << scaling;

    const QStringList fontTypes { "font-name", "monospace-font-name" };

    Q_FOREACH (const QString fontType, fontTypes) {
        gchar *fontName = g_settings_get_string(m_settings, fontType.toStdString().c_str());
        if (!fontName) {
            qCWarning(QGnomePlatform) << "Couldn't get " << fontType;
        } else {
            QString fontNameString(fontName);
            QRegExp re("(.+)[ \t]+([0-9]+)");
            int fontSize;
            if (re.indexIn(fontNameString) == 0) {
                fontSize = re.cap(2).toInt();
                if (fontType == QLatin1String("font-name")) {
                    m_fonts[QPlatformTheme::SystemFont] = new QFont(re.cap(1), fontSize * scaling, QFont::Normal);
                    qCDebug(QGnomePlatform) << "Font name: " << re.cap(1) << " (size " << fontSize << ")";
                } else if (fontType == QLatin1String("monospace-font-name")) {
                    m_fonts[QPlatformTheme::FixedFont] = new QFont(re.cap(1), fontSize * scaling, QFont::Normal);
                    qCDebug(QGnomePlatform) << "Monospace font name: " << re.cap(1) << " (size " << fontSize << ")";
                }
            } else {
                if (fontType == QLatin1String("font-name")) {
                    m_fonts[QPlatformTheme::SystemFont] = new QFont(fontNameString);
                    qCDebug(QGnomePlatform) << "Font name: " << fontNameString;
                } else if (fontType == QLatin1String("monospace-font-name")) {
                    m_fonts[QPlatformTheme::FixedFont] = new QFont(fontNameString);
                    qCDebug(QGnomePlatform) << "Monospace font name: " << fontNameString;
                }
            }
            free(fontName);
        }
    }
}

void GnomeHintsSettings::loadPalette()
{
    if (m_palette) {
        delete m_palette;
        m_palette = nullptr;
    }

    m_palette = new QPalette();
//     GtkCssProvider *gtkCssProvider = gtk_css_provider_get_named(themeName, preferDark ? "dark" : NULL);
//
//     if (!gtkCssProvider) {
//         qCDebug(QGnomePlatform) << "Couldn't load current gtk css provider!";
//         return;
//     }

//     qCDebug(QGnomePlatform) << gtk_css_provider_to_string(gtkCssProvider);
}

QStringList GnomeHintsSettings::xdgIconThemePaths() const
{
    QStringList paths;

    const QFileInfo homeIconDir(QDir::homePath() + QStringLiteral("/.icons"));
    if (homeIconDir.isDir()) {
        paths << homeIconDir.absoluteFilePath();
    }

    QString xdgDirString = QFile::decodeName(qgetenv("XDG_DATA_DIRS"));

    if (xdgDirString.isEmpty()) {
        xdgDirString = QStringLiteral("/usr/local/share:/usr/share");
    }

    Q_FOREACH (const QString &xdgDir, xdgDirString.split(QLatin1Char(':'))) {
        const QFileInfo xdgIconsDir(xdgDir + QStringLiteral("/icons"));
        if (xdgIconsDir.isDir()) {
            paths << xdgIconsDir.absoluteFilePath();
        }
    }

    return paths;
}
<commit_msg>Avoid using gtk+ style<commit_after>/*
 * Copyright (C) 2016 Jan Grulich
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "gnomehintssettings.h"

#include <QDir>
#include <QString>
#include <QPalette>
#include <QMainWindow>
#include <QApplication>
#include <QGuiApplication>
#include <QDialogButtonBox>
#include <QToolBar>
#include <QLoggingCategory>

#include <gtk-3.0/gtk/gtksettings.h>

Q_LOGGING_CATEGORY(QGnomePlatform, "qt.qpa.qgnomeplatform")

GnomeHintsSettings::GnomeHintsSettings()
    : QObject(0)
    , m_gtkThemeDarkVariant(false)
    , m_palette(nullptr)
    , m_settings(g_settings_new("org.gnome.desktop.interface"))
{
    gtk_init(nullptr, nullptr);

    // Get current theme and variant
    // g_object_get(gtk_settings_get_default(), "gtk-theme-name", &m_gtkTheme, NULL);
    m_gtkTheme = g_settings_get_string(m_settings, "gtk-theme");
    g_object_get(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", &m_gtkThemeDarkVariant, NULL);

    if (!m_gtkTheme) {
        qCWarning(QGnomePlatform) << "Couldn't get current gtk theme!";
    } else {
        qCDebug(QGnomePlatform) << "Theme name: " << m_gtkTheme;
        qCDebug(QGnomePlatform) << "Dark version: " << (m_gtkThemeDarkVariant ? "yes" : "no");
    }

    gint cursorBlinkTime = g_settings_get_int(m_settings, "cursor-blink-time");
//     g_object_get(gtk_settings_get_default(), "gtk-cursor-blink-time", &cursorBlinkTime, NULL);
    if (cursorBlinkTime >= 100) {
        qCDebug(QGnomePlatform) << "Cursor blink time: " << cursorBlinkTime;
        m_hints[QPlatformTheme::CursorFlashTime] = cursorBlinkTime;
    } else {
        m_hints[QPlatformTheme::CursorFlashTime] = 1200;
    }

    gint doubleClickTime = 400;
    g_object_get(gtk_settings_get_default(), "gtk-double-click-time", &doubleClickTime, NULL);
    qCDebug(QGnomePlatform) << "Double click time: " << doubleClickTime;
    m_hints[QPlatformTheme::MouseDoubleClickInterval] = doubleClickTime;

    guint longPressTime = 500;
    g_object_get(gtk_settings_get_default(), "gtk-long-press-time", &longPressTime, NULL);
    qCDebug(QGnomePlatform) << "Long press time: " << longPressTime;
    m_hints[QPlatformTheme::MousePressAndHoldInterval] = longPressTime;

    gint doubleClickDistance = 5;
    g_object_get(gtk_settings_get_default(), "gtk-double-click-distance", &doubleClickDistance, NULL);
    qCDebug(QGnomePlatform) << "Double click distance: " << doubleClickDistance;
    m_hints[QPlatformTheme::MouseDoubleClickDistance] = doubleClickDistance;

    gint startDragDistance = 8;
    g_object_get(gtk_settings_get_default(), "gtk-dnd-drag-threshold", &startDragDistance, NULL);
    qCDebug(QGnomePlatform) << "Dnd drag threshold: " << startDragDistance;
    m_hints[QPlatformTheme::StartDragDistance] = startDragDistance;

    guint passwordMaskDelay = 0;
    g_object_get(gtk_settings_get_default(), "gtk-entry-password-hint-timeout", &passwordMaskDelay, NULL);
    qCDebug(QGnomePlatform) << "Password hint timeout: " << passwordMaskDelay;
    m_hints[QPlatformTheme::PasswordMaskDelay] = passwordMaskDelay;

    gchar *systemIconTheme = g_settings_get_string(m_settings, "icon-theme");
//     g_object_get(gtk_settings_get_default(), "gtk-icon-theme-name", &systemIconTheme, NULL);
    if (systemIconTheme) {
        qCDebug(QGnomePlatform) << "Icon theme: " << systemIconTheme;
        m_hints[QPlatformTheme::SystemIconThemeName] = systemIconTheme;
        free(systemIconTheme);
    } else {
        m_hints[QPlatformTheme::SystemIconThemeName] = "Adwaita";
    }
    m_hints[QPlatformTheme::SystemIconFallbackThemeName] = "Adwaita";
    m_hints[QPlatformTheme::IconThemeSearchPaths] = xdgIconThemePaths();

    QStringList styleNames;
    styleNames << QStringLiteral("adwaita")
               // Avoid using gtk+ style as it uses gtk2 and we use gtk3 which is causing a crash
               // << QStringLiteral("gtk+")
               << QStringLiteral("fusion")
               << QStringLiteral("windows");
    m_hints[QPlatformTheme::StyleNames] = styleNames;

    m_hints[QPlatformTheme::DialogButtonBoxLayout] = QDialogButtonBox::GnomeLayout;
    m_hints[QPlatformTheme::DialogButtonBoxButtonsHaveIcons] = true;
    m_hints[QPlatformTheme::KeyboardScheme] = QPlatformTheme::GnomeKeyboardScheme;
    m_hints[QPlatformTheme::IconPixmapSizes] = QVariant::fromValue(QList<int>() << 512 << 256 << 128 << 64 << 32 << 22 << 16 << 8);
    m_hints[QPlatformTheme::PasswordMaskCharacter] = QVariant(QChar(0x2022));

    // Watch for changes
    g_signal_connect(m_settings, "changed::gtk-theme", G_CALLBACK(gsettingPropertyChanged), this);
    g_signal_connect(m_settings, "changed::icon-theme", G_CALLBACK(gsettingPropertyChanged), this);
    g_signal_connect(m_settings, "changed::cursor-blink-time", G_CALLBACK(gsettingPropertyChanged), this);
    g_signal_connect(m_settings, "changed::font-name", G_CALLBACK(gsettingPropertyChanged), this);
    g_signal_connect(m_settings, "changed::monospace-font-name", G_CALLBACK(gsettingPropertyChanged), this);
    g_signal_connect(m_settings, "changed::text-scaling-factor", G_CALLBACK(gsettingPropertyChanged), this);

    // g_signal_connect(gtk_settings_get_default(), "notify::gtk-theme-name", G_CALLBACK(gtkThemeChanged), this);

                                        /* Other theme hints */
    // KeyboardInputInterval, StartDragTime, KeyboardAutoRepeatRate, StartDragVelocity, DropShadow,
    // MaximumScrollBarDragDistance, ItemViewActivateItemOnSingleClick, WindowAutoPlacement, DialogButtonBoxButtonsHaveIcons
    // UseFullScreenForPopupMenu, UiEffects, SpellCheckUnderlineStyle, TabFocusBehavior, TabAllWidgets, PasswordMaskCharacter
    // DialogSnapToDefaultButton, ContextMenuOnMouseRelease, WheelScrollLines
    //  TODO TextCursorWidth, ToolButtonStyle, ToolBarIconSize

    // Load fonts
    loadFonts();

    // Load palette
    loadPalette();
}

GnomeHintsSettings::~GnomeHintsSettings()
{
    qDeleteAll(m_fonts);
    delete m_palette;
}

void GnomeHintsSettings::gsettingPropertyChanged(GSettings *settings, gchar *key, GnomeHintsSettings *gnomeHintsSettings)
{
    Q_UNUSED(settings);

    const QString changedProperty = key;

    if (changedProperty == QLatin1String("gtk-theme")) {
        gnomeHintsSettings->themeChanged();
    } else if (changedProperty == QLatin1String("icon-theme")) {
        gnomeHintsSettings->iconsChanged();
    } else if (changedProperty == QLatin1String("cursor-blink-time")) {
        gnomeHintsSettings->cursorBlinkTimeChanged();
    } else if (changedProperty == QLatin1String("font-name")) {
        gnomeHintsSettings->fontChanged();
    } else if (changedProperty == QLatin1String("monospace-font-name")) {
        gnomeHintsSettings->fontChanged();
    } else if (changedProperty == QLatin1String("text-scaling-factor")) {
        gnomeHintsSettings->fontChanged();
    } else {
        qCDebug(QGnomePlatform) << "GSetting property change: " << key;
    }
}

void GnomeHintsSettings::cursorBlinkTimeChanged()
{
    gint cursorBlinkTime = g_settings_get_int(m_settings, "cursor-blink-time");
    if (cursorBlinkTime >= 100) {
        qCDebug(QGnomePlatform) << "Cursor blink time changed to: " << cursorBlinkTime;
        m_hints[QPlatformTheme::CursorFlashTime] = cursorBlinkTime;
    } else {
        qCDebug(QGnomePlatform) << "Cursor blink time changed to: 1200";
        m_hints[QPlatformTheme::CursorFlashTime] = 1200;
    }

    //If we are not a QApplication, means that we are a QGuiApplication, then we do nothing.
    if (!qobject_cast<QApplication *>(QCoreApplication::instance())) {
        return;
    }

    QWidgetList widgets = QApplication::allWidgets();
    Q_FOREACH (QWidget *widget, widgets) {
        if (qobject_cast<QToolBar *>(widget) || qobject_cast<QMainWindow *>(widget)) {
            QEvent event(QEvent::StyleChange);
            QApplication::sendEvent(widget, &event);
        }
    }
}

void GnomeHintsSettings::fontChanged()
{
    loadFonts();

    if (qobject_cast<QApplication *>(QCoreApplication::instance())) {
        QApplication::setFont(*m_fonts[QPlatformTheme::SystemFont]);
        QWidgetList widgets = QApplication::allWidgets();
        Q_FOREACH (QWidget *widget, widgets) {
            widget->setFont(*m_fonts[QPlatformTheme::SystemFont]);
        }
    } else {
        QGuiApplication::setFont(*m_fonts[QPlatformTheme::SystemFont]);
    }
}

void GnomeHintsSettings::iconsChanged()
{
    gchar *systemIconTheme = g_settings_get_string(m_settings, "icon-theme");
    if (systemIconTheme) {
        qCDebug(QGnomePlatform) << "Icon theme changed to: " << systemIconTheme;
        m_hints[QPlatformTheme::SystemIconThemeName] = systemIconTheme;
        free(systemIconTheme);
    } else {
        qCDebug(QGnomePlatform) << "Icon theme changed to: Adwaita";
        m_hints[QPlatformTheme::SystemIconThemeName] = "Adwaita";
    }

    //If we are not a QApplication, means that we are a QGuiApplication, then we do nothing.
    if (!qobject_cast<QApplication *>(QCoreApplication::instance())) {
        return;
    }

    QWidgetList widgets = QApplication::allWidgets();
    Q_FOREACH (QWidget *widget, widgets) {
        if (qobject_cast<QToolBar *>(widget) || qobject_cast<QMainWindow *>(widget)) {
            QEvent event(QEvent::StyleChange);
            QApplication::sendEvent(widget, &event);
        }
    }
}

void GnomeHintsSettings::themeChanged()
{
    loadPalette();

    // QApplication::setPalette and QGuiApplication::setPalette are different functions
    // and non virtual. Call the correct one
    if (qobject_cast<QApplication *>(QCoreApplication::instance())) {
        QApplication::setPalette(*m_palette);
    } else if (qobject_cast<QGuiApplication *>(QCoreApplication::instance())) {
        QGuiApplication::setPalette(*m_palette);
    }
}

void GnomeHintsSettings::loadFonts()
{
    gdouble scaling = g_settings_get_double(m_settings, "text-scaling-factor");
    qCDebug(QGnomePlatform) << "Font scaling: " << scaling;

    const QStringList fontTypes { "font-name", "monospace-font-name" };

    Q_FOREACH (const QString fontType, fontTypes) {
        gchar *fontName = g_settings_get_string(m_settings, fontType.toStdString().c_str());
        if (!fontName) {
            qCWarning(QGnomePlatform) << "Couldn't get " << fontType;
        } else {
            QString fontNameString(fontName);
            QRegExp re("(.+)[ \t]+([0-9]+)");
            int fontSize;
            if (re.indexIn(fontNameString) == 0) {
                fontSize = re.cap(2).toInt();
                if (fontType == QLatin1String("font-name")) {
                    m_fonts[QPlatformTheme::SystemFont] = new QFont(re.cap(1), fontSize * scaling, QFont::Normal);
                    qCDebug(QGnomePlatform) << "Font name: " << re.cap(1) << " (size " << fontSize << ")";
                } else if (fontType == QLatin1String("monospace-font-name")) {
                    m_fonts[QPlatformTheme::FixedFont] = new QFont(re.cap(1), fontSize * scaling, QFont::Normal);
                    qCDebug(QGnomePlatform) << "Monospace font name: " << re.cap(1) << " (size " << fontSize << ")";
                }
            } else {
                if (fontType == QLatin1String("font-name")) {
                    m_fonts[QPlatformTheme::SystemFont] = new QFont(fontNameString);
                    qCDebug(QGnomePlatform) << "Font name: " << fontNameString;
                } else if (fontType == QLatin1String("monospace-font-name")) {
                    m_fonts[QPlatformTheme::FixedFont] = new QFont(fontNameString);
                    qCDebug(QGnomePlatform) << "Monospace font name: " << fontNameString;
                }
            }
            free(fontName);
        }
    }
}

void GnomeHintsSettings::loadPalette()
{
    if (m_palette) {
        delete m_palette;
        m_palette = nullptr;
    }

    m_palette = new QPalette();
//     GtkCssProvider *gtkCssProvider = gtk_css_provider_get_named(themeName, preferDark ? "dark" : NULL);
//
//     if (!gtkCssProvider) {
//         qCDebug(QGnomePlatform) << "Couldn't load current gtk css provider!";
//         return;
//     }

//     qCDebug(QGnomePlatform) << gtk_css_provider_to_string(gtkCssProvider);
}

QStringList GnomeHintsSettings::xdgIconThemePaths() const
{
    QStringList paths;

    const QFileInfo homeIconDir(QDir::homePath() + QStringLiteral("/.icons"));
    if (homeIconDir.isDir()) {
        paths << homeIconDir.absoluteFilePath();
    }

    QString xdgDirString = QFile::decodeName(qgetenv("XDG_DATA_DIRS"));

    if (xdgDirString.isEmpty()) {
        xdgDirString = QStringLiteral("/usr/local/share:/usr/share");
    }

    Q_FOREACH (const QString &xdgDir, xdgDirString.split(QLatin1Char(':'))) {
        const QFileInfo xdgIconsDir(xdgDir + QStringLiteral("/icons"));
        if (xdgIconsDir.isDir()) {
            paths << xdgIconsDir.absoluteFilePath();
        }
    }

    return paths;
}
<|endoftext|>