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

#include "sync/sync_user.hpp"

#include "sync/impl/sync_metadata.hpp"
#include "sync/sync_manager.hpp"
#include "sync/sync_session.hpp"

namespace realm {

SyncUser::SyncUser(std::string refresh_token,
                   std::string identity,
                   util::Optional<std::string> server_url,
                   bool is_admin)
: m_state(State::Active)
, m_server_url(server_url.value_or(""))
, m_is_admin(is_admin)
, m_refresh_token(std::move(refresh_token))
, m_identity(std::move(identity))
{
    if (!is_admin) {
        SyncManager::shared().perform_metadata_update([this, server_url=std::move(server_url)](const auto& manager) {
            auto metadata = SyncUserMetadata(manager, m_identity);
            metadata.set_state(server_url, m_refresh_token);
        });
    }
}

std::vector<std::shared_ptr<SyncSession>> SyncUser::all_sessions()
{
    std::lock_guard<std::mutex> lock(m_mutex);
    std::vector<std::shared_ptr<SyncSession>> sessions;
    if (m_state == State::Error) {
        return sessions;
    }
    for (auto it = m_sessions.begin(); it != m_sessions.end();) {
        if (auto ptr = it->second.lock()) {
            if (ptr->is_valid()) {
                sessions.emplace_back(std::move(ptr));
                it++;
                continue;
            }
        }
        // This session is bad, destroy it.
        it = m_sessions.erase(it);
    }
    return sessions;
}

std::shared_ptr<SyncSession> SyncUser::session_for_url(const std::string& url)
{
    std::lock_guard<std::mutex> lock(m_mutex);
    if (m_state == State::Error) {
        return nullptr;
    }
    auto it = m_sessions.find(url);
    if (it == m_sessions.end()) {
        return nullptr;
    }
    auto locked = it->second.lock();
    if (!locked) {
        // Remove the session from the map, because it has fatally errored out or the entry is invalid.
        m_sessions.erase(it);
    }
    return locked;
}

void SyncUser::update_refresh_token(std::string token)
{
    std::vector<std::shared_ptr<SyncSession>> sessions_to_revive;
    {
        std::unique_lock<std::mutex> lock(m_mutex);
        switch (m_state) {
            case State::Error:
                return;
            case State::Active:
                m_refresh_token = token;
                break;
            case State::LoggedOut: {
                sessions_to_revive.reserve(m_waiting_sessions.size());
                m_refresh_token = token;
                m_state = State::Active;
                for (auto& pair : m_waiting_sessions) {
                    if (auto ptr = pair.second.lock()) {
                        m_sessions[pair.first] = ptr;
                        sessions_to_revive.emplace_back(std::move(ptr));
                    }
                }
                m_waiting_sessions.clear();
                break;
            }
        }
        // Update persistent user metadata.
        if (!m_is_admin) {
            SyncManager::shared().perform_metadata_update([=](const auto& manager) {
                auto metadata = SyncUserMetadata(manager, m_identity);
                metadata.set_state(m_server_url, token);
            });
        }
    }
    // (Re)activate all pending sessions.
    // Note that we do this after releasing the lock, since the session may
    // need to access protected User state in the process of binding itself.
    for (auto& session : sessions_to_revive) {
        SyncSession::revive_if_needed(session);
    }
}

void SyncUser::log_out()
{
    if (m_is_admin) {
        // Admin users cannot be logged out.
        return;
    }
    std::lock_guard<std::mutex> lock(m_mutex);
    if (m_state == State::LoggedOut) {
        return;
    }
    m_state = State::LoggedOut;
    // Move all active sessions into the waiting sessions pool. If the user is
    // logged back in, they will automatically be reactivated.
    for (auto& pair : m_sessions) {
        if (auto ptr = pair.second.lock()) {
            ptr->log_out();
            m_waiting_sessions[pair.first] = ptr;
        }
    }
    m_sessions.clear();
    // Mark the user as 'dead' in the persisted metadata Realm.
    if (!m_is_admin) {
        SyncManager::shared().perform_metadata_update([=](const auto& manager) {
            auto metadata = SyncUserMetadata(manager, m_identity, false);
            metadata.mark_for_removal();
        });
    }
}

void SyncUser::invalidate()
{
    std::lock_guard<std::mutex> lock(m_mutex);
    m_state = State::Error;
}

std::string SyncUser::refresh_token() const
{
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_refresh_token;
}

SyncUser::State SyncUser::state() const
{
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_state;
}

void SyncUser::register_session(std::shared_ptr<SyncSession> session)
{
    const std::string& url = session->config().realm_url;
    std::unique_lock<std::mutex> lock(m_mutex);
    if (m_sessions.find(url) != m_sessions.end() || m_waiting_sessions.find(url) != m_waiting_sessions.end()) {
        throw std::invalid_argument("Can only register sessions that haven't previously been registered.");
    }
    switch (m_state) {
        case State::Active:
            // Immediately ask the session to come online.
            m_sessions[url] = session;
            if (m_is_admin) {
                session->bind_with_admin_token(m_refresh_token, session->config().realm_url);
            } else {
                lock.unlock();
                SyncSession::revive_if_needed(std::move(session));
            }
            break;
        case State::LoggedOut:
            m_waiting_sessions[url] = session;
            break;
        case State::Error:
            break;
    }
}

}
<commit_msg>fix check for already-registered sessions in SyncUser::register_session<commit_after>////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

#include "sync/sync_user.hpp"

#include "sync/impl/sync_metadata.hpp"
#include "sync/sync_manager.hpp"
#include "sync/sync_session.hpp"

namespace realm {

SyncUser::SyncUser(std::string refresh_token,
                   std::string identity,
                   util::Optional<std::string> server_url,
                   bool is_admin)
: m_state(State::Active)
, m_server_url(server_url.value_or(""))
, m_is_admin(is_admin)
, m_refresh_token(std::move(refresh_token))
, m_identity(std::move(identity))
{
    if (!is_admin) {
        SyncManager::shared().perform_metadata_update([this, server_url=std::move(server_url)](const auto& manager) {
            auto metadata = SyncUserMetadata(manager, m_identity);
            metadata.set_state(server_url, m_refresh_token);
        });
    }
}

std::vector<std::shared_ptr<SyncSession>> SyncUser::all_sessions()
{
    std::lock_guard<std::mutex> lock(m_mutex);
    std::vector<std::shared_ptr<SyncSession>> sessions;
    if (m_state == State::Error) {
        return sessions;
    }
    for (auto it = m_sessions.begin(); it != m_sessions.end();) {
        if (auto ptr = it->second.lock()) {
            if (ptr->is_valid()) {
                sessions.emplace_back(std::move(ptr));
                it++;
                continue;
            }
        }
        // This session is bad, destroy it.
        it = m_sessions.erase(it);
    }
    return sessions;
}

std::shared_ptr<SyncSession> SyncUser::session_for_url(const std::string& url)
{
    std::lock_guard<std::mutex> lock(m_mutex);
    if (m_state == State::Error) {
        return nullptr;
    }
    auto it = m_sessions.find(url);
    if (it == m_sessions.end()) {
        return nullptr;
    }
    auto locked = it->second.lock();
    if (!locked) {
        // Remove the session from the map, because it has fatally errored out or the entry is invalid.
        m_sessions.erase(it);
    }
    return locked;
}

void SyncUser::update_refresh_token(std::string token)
{
    std::vector<std::shared_ptr<SyncSession>> sessions_to_revive;
    {
        std::unique_lock<std::mutex> lock(m_mutex);
        switch (m_state) {
            case State::Error:
                return;
            case State::Active:
                m_refresh_token = token;
                break;
            case State::LoggedOut: {
                sessions_to_revive.reserve(m_waiting_sessions.size());
                m_refresh_token = token;
                m_state = State::Active;
                for (auto& pair : m_waiting_sessions) {
                    if (auto ptr = pair.second.lock()) {
                        m_sessions[pair.first] = ptr;
                        sessions_to_revive.emplace_back(std::move(ptr));
                    }
                }
                m_waiting_sessions.clear();
                break;
            }
        }
        // Update persistent user metadata.
        if (!m_is_admin) {
            SyncManager::shared().perform_metadata_update([=](const auto& manager) {
                auto metadata = SyncUserMetadata(manager, m_identity);
                metadata.set_state(m_server_url, token);
            });
        }
    }
    // (Re)activate all pending sessions.
    // Note that we do this after releasing the lock, since the session may
    // need to access protected User state in the process of binding itself.
    for (auto& session : sessions_to_revive) {
        SyncSession::revive_if_needed(session);
    }
}

void SyncUser::log_out()
{
    if (m_is_admin) {
        // Admin users cannot be logged out.
        return;
    }
    std::lock_guard<std::mutex> lock(m_mutex);
    if (m_state == State::LoggedOut) {
        return;
    }
    m_state = State::LoggedOut;
    // Move all active sessions into the waiting sessions pool. If the user is
    // logged back in, they will automatically be reactivated.
    for (auto& pair : m_sessions) {
        if (auto ptr = pair.second.lock()) {
            ptr->log_out();
            m_waiting_sessions[pair.first] = ptr;
        }
    }
    m_sessions.clear();
    // Mark the user as 'dead' in the persisted metadata Realm.
    if (!m_is_admin) {
        SyncManager::shared().perform_metadata_update([=](const auto& manager) {
            auto metadata = SyncUserMetadata(manager, m_identity, false);
            metadata.mark_for_removal();
        });
    }
}

void SyncUser::invalidate()
{
    std::lock_guard<std::mutex> lock(m_mutex);
    m_state = State::Error;
}

std::string SyncUser::refresh_token() const
{
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_refresh_token;
}

SyncUser::State SyncUser::state() const
{
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_state;
}

void SyncUser::register_session(std::shared_ptr<SyncSession> session)
{
    const std::string& url = session->config().realm_url;
    std::unique_lock<std::mutex> lock(m_mutex);
    auto has_session = [&] (const auto& sessions) {
        auto it = sessions.find(url);
        return it != sessions.end() && !it->second.expired();
    };
    if (has_session(m_sessions) || has_session(m_waiting_sessions)) {
        throw std::invalid_argument("Can only register sessions that haven't previously been registered.");
    }
    switch (m_state) {
        case State::Active:
            // Immediately ask the session to come online.
            m_sessions[url] = session;
            if (m_is_admin) {
                session->bind_with_admin_token(m_refresh_token, session->config().realm_url);
            } else {
                lock.unlock();
                SyncSession::revive_if_needed(std::move(session));
            }
            break;
        case State::LoggedOut:
            m_waiting_sessions[url] = session;
            break;
        case State::Error:
            break;
    }
}

}
<|endoftext|>


<commit_before>#include <boost/test/unit_test.hpp>

#include <string>
#include <vector>

#include "key.h"
#include "base58.h"
#include "uint256.h"
#include "util.h"

using namespace std;

static const string strSecret1 ("5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj");
static const string strSecret2 ("5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3");
static const string strSecret1C("Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw");
static const string strSecret2C("L3Hq7a8FEQwJkW1M2GNKDW28546Vp5miewcCzSqUD9kCAXrJdS3g");
static const string strAddress1("1HV9Lc3sNHZxwj4Zk6fB38tEmBryq2cBiF");

#ifdef KEY_TESTS_DUMPINFO
void dumpKeyInfo(uint256 privkey)
{
    CSecret secret;
    secret.resize(32);
    memcpy(&secret[0], &privkey, 32);
    vector<unsigned char> sec;
    sec.resize(32);
    memcpy(&sec[0], &secret[0], 32);
    printf("  * secret (hex): %s\n", HexStr(sec).c_str());

    for (int nCompressed=0; nCompressed<2; nCompressed++)
    {
        bool fCompressed = nCompressed == 1;
        printf("  * %s:\n", fCompressed ? "compressed" : "uncompressed");
        CBitcoinSecret bsecret;
        bsecret.SetSecret(secret, fCompressed);
        printf("    * secret (base58): %s\n", bsecret.ToString().c_str());
        CKey key;
        key.SetSecret(secret, fCompressed);
        vector<unsigned char> vchPubKey = key.GetPubKey();
        printf("    * pubkey (hex): %s\n", HexStr(vchPubKey).c_str());
        printf("    * address (base58): %s\n", CBitcoinAddress(vchPubKey).ToString().c_str());
    }
}
#endif

BOOST_AUTO_TEST_SUITE(key_tests)

BOOST_AUTO_TEST_CASE(key_test1)
{
    CBitcoinSecret bsecret1, bsecret2, bsecret1C, bsecret2C, baddress1;
    BOOST_CHECK( bsecret1.SetString (strSecret1));
    BOOST_CHECK( bsecret2.SetString (strSecret2));
    BOOST_CHECK( bsecret1C.SetString(strSecret1C));
    BOOST_CHECK( bsecret2C.SetString(strSecret2C));
    BOOST_CHECK(!baddress1.SetString(strAddress1));

    bool fCompressed;
    CSecret secret1  = bsecret1.GetSecret (fCompressed);
    BOOST_CHECK(fCompressed == false);
    CSecret secret2  = bsecret2.GetSecret (fCompressed);
    BOOST_CHECK(fCompressed == false);
    CSecret secret1C = bsecret1C.GetSecret(fCompressed);
    BOOST_CHECK(fCompressed == true);
    CSecret secret2C = bsecret2C.GetSecret(fCompressed);
    BOOST_CHECK(fCompressed == true);

    BOOST_CHECK(secret1 == secret1C);
    BOOST_CHECK(secret2 == secret2C);

    CKey key1, key2, key1C, key2C;
    key1.SetSecret(secret1, false);
    key2.SetSecret(secret2, false);
    key1C.SetSecret(secret1, true);
    key2C.SetSecret(secret2, true);

    BOOST_CHECK(CBitcoinAddress(key1.GetPubKey ()).ToString() == "1QFqqMUD55ZV3PJEJZtaKCsQmjLT6JkjvJ");
    BOOST_CHECK(CBitcoinAddress(key2.GetPubKey ()).ToString() == "1F5y5E5FMc5YzdJtB9hLaUe43GDxEKXENJ");
    BOOST_CHECK(CBitcoinAddress(key1C.GetPubKey()).ToString() == "1NoJrossxPBKfCHuJXT4HadJrXRE9Fxiqs");
    BOOST_CHECK(CBitcoinAddress(key2C.GetPubKey()).ToString() == "1CRj2HyM1CXWzHAXLQtiGLyggNT9WQqsDs");

    for (int n=0; n<16; n++)
    {
        string strMsg = strprintf("Very secret message %i: 11", n);
        uint256 hashMsg = Hash(strMsg.begin(), strMsg.end());

        // normal signatures

        vector<unsigned char> sign1, sign2, sign1C, sign2C;

        BOOST_CHECK(key1.Sign (hashMsg, sign1));
        BOOST_CHECK(key2.Sign (hashMsg, sign2));
        BOOST_CHECK(key1C.Sign(hashMsg, sign1C));
        BOOST_CHECK(key2C.Sign(hashMsg, sign2C));

        BOOST_CHECK( key1.Verify(hashMsg, sign1));
        BOOST_CHECK(!key1.Verify(hashMsg, sign2));
        BOOST_CHECK( key1.Verify(hashMsg, sign1C));
        BOOST_CHECK(!key1.Verify(hashMsg, sign2C));

        BOOST_CHECK(!key2.Verify(hashMsg, sign1));
        BOOST_CHECK( key2.Verify(hashMsg, sign2));
        BOOST_CHECK(!key2.Verify(hashMsg, sign1C));
        BOOST_CHECK( key2.Verify(hashMsg, sign2C));

        BOOST_CHECK( key1C.Verify(hashMsg, sign1));
        BOOST_CHECK(!key1C.Verify(hashMsg, sign2));
        BOOST_CHECK( key1C.Verify(hashMsg, sign1C));
        BOOST_CHECK(!key1C.Verify(hashMsg, sign2C));

        BOOST_CHECK(!key2C.Verify(hashMsg, sign1));
        BOOST_CHECK( key2C.Verify(hashMsg, sign2));
        BOOST_CHECK(!key2C.Verify(hashMsg, sign1C));
        BOOST_CHECK( key2C.Verify(hashMsg, sign2C));

        // compact signatures (with key recovery)

        vector<unsigned char> csign1, csign2, csign1C, csign2C;

        BOOST_CHECK(key1.SignCompact (hashMsg, csign1));
        BOOST_CHECK(key2.SignCompact (hashMsg, csign2));
        BOOST_CHECK(key1C.SignCompact(hashMsg, csign1C));
        BOOST_CHECK(key2C.SignCompact(hashMsg, csign2C));

        CKey rkey1, rkey2, rkey1C, rkey2C;

        BOOST_CHECK(rkey1.SetCompactSignature (hashMsg, csign1));
        BOOST_CHECK(rkey2.SetCompactSignature (hashMsg, csign2));
        BOOST_CHECK(rkey1C.SetCompactSignature(hashMsg, csign1C));
        BOOST_CHECK(rkey2C.SetCompactSignature(hashMsg, csign2C));

        BOOST_CHECK(rkey1.GetPubKey()  == key1.GetPubKey());
        BOOST_CHECK(rkey2.GetPubKey()  == key2.GetPubKey());
        BOOST_CHECK(rkey1C.GetPubKey() == key1C.GetPubKey());
        BOOST_CHECK(rkey2C.GetPubKey() == key2C.GetPubKey());
    }
}

BOOST_AUTO_TEST_SUITE_END()
<commit_msg>fix key tests<commit_after>#include <boost/test/unit_test.hpp>

#include <string>
#include <vector>

#include "key.h"
#include "base58.h"
#include "uint256.h"
#include "util.h"

using namespace std;

static const string strSecret1C("VM9UvunstWDskq3huuqPEtTUBUkoPPSeUaWLrpfyZvDFU5moV3LC");
static const string strSecret2C("VKeYK9DPt2gi7TQYeyEm5Bxs7Rt9TW4DyisFAXwSn8r84G7e482e");
static const string strAddress1("1HV9Lc3sNHZxwj4Zk6fB38tEmBryq2cBiF");

#ifdef KEY_TESTS_DUMPINFO
void dumpKeyInfo(uint256 privkey)
{
    CSecret secret;
    secret.resize(32);
    memcpy(&secret[0], &privkey, 32);
    vector<unsigned char> sec;
    sec.resize(32);
    memcpy(&sec[0], &secret[0], 32);
    printf("  * secret (hex): %s\n", HexStr(sec).c_str());

    for (int nCompressed=0; nCompressed<2; nCompressed++)
    {
        bool fCompressed = nCompressed == 1;
        printf("  * %s:\n", fCompressed ? "compressed" : "uncompressed");
        CBitcoinSecret bsecret;
        bsecret.SetSecret(secret, fCompressed);
        printf("    * secret (base58): %s\n", bsecret.ToString().c_str());
        CKey key;
        key.SetSecret(secret, fCompressed);
        vector<unsigned char> vchPubKey = key.GetPubKey();
        printf("    * pubkey (hex): %s\n", HexStr(vchPubKey).c_str());
        printf("    * address (base58): %s\n", CBitcoinAddress(vchPubKey).ToString().c_str());
    }
}
#endif

BOOST_AUTO_TEST_SUITE(key_tests)

BOOST_AUTO_TEST_CASE(key_test1)
{
    CBitcoinSecret bsecret1C, bsecret2C, baddress1;
    BOOST_CHECK( bsecret1C.SetString(strSecret1C));
    BOOST_CHECK( bsecret2C.SetString(strSecret2C));
    BOOST_CHECK(!baddress1.SetString(strAddress1));

    bool fCompressed;
    CSecret secret1C = bsecret1C.GetSecret(fCompressed);
    BOOST_CHECK(fCompressed == true);
    CSecret secret2C = bsecret2C.GetSecret(fCompressed);
    BOOST_CHECK(fCompressed == true);

    CKey key1C, key2C;
    key1C.SetSecret(secret1C, true);
    key2C.SetSecret(secret2C, true);

    BOOST_CHECK(CBitcoinAddress(key1C.GetPubKey()).ToString() == "SccFDmEQGofxbE7mU7v53X7ydLHNLxjr5K");
    BOOST_CHECK(CBitcoinAddress(key2C.GetPubKey()).ToString() == "Sco97ZgVEFFKSyYxoaGF2s3w1zZo9mfBMi");

    for (int n=0; n<16; n++)
    {
        string strMsg = strprintf("Very secret message %i: 11", n);
        uint256 hashMsg = Hash(strMsg.begin(), strMsg.end());

        // normal signatures

        vector<unsigned char> sign1C, sign2C;

        BOOST_CHECK(key1C.Sign(hashMsg, sign1C));
        BOOST_CHECK(key2C.Sign(hashMsg, sign2C));

        BOOST_CHECK( key1C.Verify(hashMsg, sign1C));
        BOOST_CHECK(!key1C.Verify(hashMsg, sign2C));

        BOOST_CHECK(!key2C.Verify(hashMsg, sign1C));
        BOOST_CHECK( key2C.Verify(hashMsg, sign2C));

        // compact signatures (with key recovery)

        vector<unsigned char> csign1C, csign2C;

        BOOST_CHECK(key1C.SignCompact(hashMsg, csign1C));
        BOOST_CHECK(key2C.SignCompact(hashMsg, csign2C));

        CKey rkey1C, rkey2C;

        BOOST_CHECK(rkey1C.SetCompactSignature(hashMsg, csign1C));
        BOOST_CHECK(rkey2C.SetCompactSignature(hashMsg, csign2C));

        BOOST_CHECK(rkey1C.GetPubKey() == key1C.GetPubKey());
        BOOST_CHECK(rkey2C.GetPubKey() == key2C.GetPubKey());
    }
}

BOOST_AUTO_TEST_SUITE_END()
<|endoftext|>


<commit_before>/**
 * @file
 *
 * @brief
 *
 * @copyright BSD License (see doc/LICENSE.md or https://www.libelektra.org)
 */

#include <iostream>
#include <memory>
#include <string>
#include <vector>

#include <coloredkdbio.hpp>

#include <cmdline.hpp>
#include <command.hpp>
#include <external.hpp>
#include <factory.hpp>
#include <signal.h>
#include <toolexcept.hpp>

#include <kdb.hpp>
#include <key.hpp>

using namespace kdb;
using namespace std;

int displayHelp (std::string app, Factory const & f)
{
	std::cout << "Usage: " << app << " <command> [args]\n" << std::endl;
	std::cout << app << " is a program to manage elektra's key database.\n"
		  << "Please run a command with -H or --help as args to show a help text for\n"
		  << "a specific command.\n"
		  << std::endl;
	std::cout << "Known commands are:" << std::endl;
	std::vector<std::string> commands;
	try
	{
		commands = f.getPrettyCommands ();
	}
	catch (kdb::KDBException const & ce)
	{
		std::cerr << "Sorry, I have a severe problem, it seems like I am not installed correctly!\n"
			  << "kdbOpen() failed with the info:" << std::endl
			  << ce.what () << std::endl
			  << "Please report the issue at https://issues.libelektra.org/";
		return 8;
	}
	for (auto & command : commands)
	{
		std::cout << command << std::endl;
	}
	return 0;
}

void displayVersion ()
{
	kdb::KDB kdb;
	kdb::KeySet versions;
	kdb::Key k ("system/elektra/version", KEY_END);
	kdb.get (versions, k);
	kdb::Key kdb_version = versions.lookup ("system/elektra/version/constants/KDB_VERSION");
	if (!kdb_version)
	{
		cerr << "Could not lookup KDB_VERSION key" << endl;
	}
	else
	{
		cout << "KDB_VERSION: " << kdb_version.getString () << endl;
	}
	kdb::Key so_version = versions.lookup ("system/elektra/version/constants/SO_VERSION");
	if (!so_version)
	{
		cerr << "Could not lookup SO_VERSION key" << endl;
	}
	else
	{
		cout << "SO_VERSION: " << so_version.getString () << endl;
	}
}

void printSignal (int signum)
{
	switch (signum)
	{
	case SIGILL:
		cerr << "SIGILL";
		break;
	case SIGABRT:
		cerr << "SIGABRT";
		break;
	case SIGFPE:
		cerr << "SIGFPE";
		break;
	case SIGSEGV:
		cerr << "SIGSEGV";
		break;
	}
}

void catchSignal (int signum)
{
	cerr << endl << "Sorry, I crashed by the signal ";
	printSignal (signum);
	cerr << endl << "This should not have happened!" << endl;
	cerr << endl << "Please report the issue at https://issues.libelektra.org/" << std::endl;
	signal (SIGABRT, SIG_DFL);
	abort ();
}

void setupSignal (int signum)
{
	if (signal (signum, catchSignal) == SIG_ERR)
	{
		cerr << "Sorry, I could not setup signal ";
		printSignal (signum);
		cerr << " because: " << strerror (errno) << std::endl;
		cerr << "Please report the issue at https://issues.libelektra.org/" << std::endl;
	}
}

void setupSignals ()
{
	setupSignal (SIGILL);
	setupSignal (SIGABRT);
	setupSignal (SIGFPE);
	setupSignal (SIGSEGV);
}

int main (int argc, char ** argv)
{
	setupSignals ();
	Factory f;

	if (argc < 2)
	{
		return displayHelp (argv[0], f);
	}

	string command = argv[1];
	if (command == "help" || command == "-H" || command == "--help")
	{
		if (argc >= 3)
		{
			runManPage (argv[2]);
		}
		else
		{
			runManPage ();
		}

		return displayHelp (argv[0], f);
	}

	if (command == "-V" || command == "--version")
	{
		displayVersion ();
		return 0;
	}

	try
	{
		std::vector<char *> origArguments (argv + 1, argv + argc);
		origArguments.push_back (0);
		CommandPtr cmd = f.get (command);
		Cmdline cl (argc, argv, cmd.get ());

		if (cl.help)
		{
			runManPage (command, cl.profile);
			// does not return, but may throw
		}

		// version and invalidOpt might be implemented
		// differently for external command
		if (dynamic_cast<ExternalCommand *> (cmd.get ()))
		{
			tryExternalCommand (&origArguments[0]);
			// does not return, but may throw
		}

		if (cl.version)
		{
			displayVersion ();
			return 0;
		}

		if (cl.invalidOpt)
		{
			cerr << cl << endl;
			return 1;
		}

		try
		{
			return cmd->execute (cl);
		}
		catch (std::invalid_argument const & ia)
		{
			cerr << "Invalid arguments passed: " << ia.what () << endl << endl;
			cerr << cl << endl;
			return 2;
		}
	}
	catch (CommandException const & ce)
	{
		std::cerr << "The command " << getErrorColor (ANSI_COLOR::BOLD) << argv[0] << " " << command
			  << getErrorColor (ANSI_COLOR::RESET) << " terminated " << getErrorColor (ANSI_COLOR::RED) << "unsuccessfully"
			  << getErrorColor (ANSI_COLOR::RESET) << " with the info:\n"
			  << ce.what () << std::endl;
		return 3;
	}
	catch (UnknownCommand const & uc)
	{
		std::cerr << "The command " << getErrorColor (ANSI_COLOR::BOLD) << argv[0] << " " << command
			  << getErrorColor (ANSI_COLOR::RESET) << " is " << getErrorColor (ANSI_COLOR::RED) << "not known"
			  << getErrorColor (ANSI_COLOR::RESET) << std::endl;
		displayHelp (argv[0], f);
		return 4;
	}
	catch (kdb::KDBException const & ce)
	{
		std::cerr << "The command " << getErrorColor (ANSI_COLOR::BOLD) << argv[0] << " " << command
			  << getErrorColor (ANSI_COLOR::RESET) << getErrorColor (ANSI_COLOR::RED) << " failed"
			  << getErrorColor (ANSI_COLOR::RESET) << " while accessing the key database with the info:\n"
			  << ce.what () << std::endl;
		return 5;
	}
	catch (std::exception const & ce)
	{
		std::cerr << "The command " << getErrorColor (ANSI_COLOR::BOLD) << argv[0] << " " << command
			  << getErrorColor (ANSI_COLOR::RESET) << " terminated " << getErrorColor (ANSI_COLOR::RED) << "unsuccessfully"
			  << getErrorColor (ANSI_COLOR::RESET) << " with the info:\n"
			  << ce.what () << "Please report the issue at https://issues.libelektra.org/" << std::endl;
		return 6;
	}
	catch (...)
	{
		std::cerr << "The command " << getErrorColor (ANSI_COLOR::BOLD) << argv[0] << " " << command
			  << getErrorColor (ANSI_COLOR::RESET) << " terminated with an " << getErrorColor (ANSI_COLOR::RED)
			  << "unknown error" << getErrorColor (ANSI_COLOR::RESET)
			  << "Please report the issue at https://issues.libelektra.org/" << std::endl;
		displayHelp (argv[0], f);
		return 7;
	}
}
<commit_msg>kdb: add newline before last line<commit_after>/**
 * @file
 *
 * @brief
 *
 * @copyright BSD License (see doc/LICENSE.md or https://www.libelektra.org)
 */

#include <iostream>
#include <memory>
#include <string>
#include <vector>

#include <coloredkdbio.hpp>

#include <cmdline.hpp>
#include <command.hpp>
#include <external.hpp>
#include <factory.hpp>
#include <signal.h>
#include <toolexcept.hpp>

#include <kdb.hpp>
#include <key.hpp>

using namespace kdb;
using namespace std;

int displayHelp (std::string app, Factory const & f)
{
	std::cout << "Usage: " << app << " <command> [args]\n" << std::endl;
	std::cout << app << " is a program to manage elektra's key database.\n"
		  << "Please run a command with -H or --help as args to show a help text for\n"
		  << "a specific command.\n"
		  << std::endl;
	std::cout << "Known commands are:" << std::endl;
	std::vector<std::string> commands;
	try
	{
		commands = f.getPrettyCommands ();
	}
	catch (kdb::KDBException const & ce)
	{
		std::cerr << "Sorry, I have a severe problem, it seems like I am not installed correctly!\n"
			  << "kdbOpen() failed with the info:" << std::endl
			  << ce.what () << std::endl
			  << "Please report the issue at https://issues.libelektra.org/";
		return 8;
	}
	for (auto & command : commands)
	{
		std::cout << command << std::endl;
	}
	return 0;
}

void displayVersion ()
{
	kdb::KDB kdb;
	kdb::KeySet versions;
	kdb::Key k ("system/elektra/version", KEY_END);
	kdb.get (versions, k);
	kdb::Key kdb_version = versions.lookup ("system/elektra/version/constants/KDB_VERSION");
	if (!kdb_version)
	{
		cerr << "Could not lookup KDB_VERSION key" << endl;
	}
	else
	{
		cout << "KDB_VERSION: " << kdb_version.getString () << endl;
	}
	kdb::Key so_version = versions.lookup ("system/elektra/version/constants/SO_VERSION");
	if (!so_version)
	{
		cerr << "Could not lookup SO_VERSION key" << endl;
	}
	else
	{
		cout << "SO_VERSION: " << so_version.getString () << endl;
	}
}

void printSignal (int signum)
{
	switch (signum)
	{
	case SIGILL:
		cerr << "SIGILL";
		break;
	case SIGABRT:
		cerr << "SIGABRT";
		break;
	case SIGFPE:
		cerr << "SIGFPE";
		break;
	case SIGSEGV:
		cerr << "SIGSEGV";
		break;
	}
}

void catchSignal (int signum)
{
	cerr << endl << "Sorry, I crashed by the signal ";
	printSignal (signum);
	cerr << endl << "This should not have happened!" << endl;
	cerr << endl << "Please report the issue at https://issues.libelektra.org/" << std::endl;
	signal (SIGABRT, SIG_DFL);
	abort ();
}

void setupSignal (int signum)
{
	if (signal (signum, catchSignal) == SIG_ERR)
	{
		cerr << "Sorry, I could not setup signal ";
		printSignal (signum);
		cerr << " because: " << strerror (errno) << std::endl;
		cerr << "Please report the issue at https://issues.libelektra.org/" << std::endl;
	}
}

void setupSignals ()
{
	setupSignal (SIGILL);
	setupSignal (SIGABRT);
	setupSignal (SIGFPE);
	setupSignal (SIGSEGV);
}

int main (int argc, char ** argv)
{
	setupSignals ();
	Factory f;

	if (argc < 2)
	{
		return displayHelp (argv[0], f);
	}

	string command = argv[1];
	if (command == "help" || command == "-H" || command == "--help")
	{
		if (argc >= 3)
		{
			runManPage (argv[2]);
		}
		else
		{
			runManPage ();
		}

		return displayHelp (argv[0], f);
	}

	if (command == "-V" || command == "--version")
	{
		displayVersion ();
		return 0;
	}

	try
	{
		std::vector<char *> origArguments (argv + 1, argv + argc);
		origArguments.push_back (0);
		CommandPtr cmd = f.get (command);
		Cmdline cl (argc, argv, cmd.get ());

		if (cl.help)
		{
			runManPage (command, cl.profile);
			// does not return, but may throw
		}

		// version and invalidOpt might be implemented
		// differently for external command
		if (dynamic_cast<ExternalCommand *> (cmd.get ()))
		{
			tryExternalCommand (&origArguments[0]);
			// does not return, but may throw
		}

		if (cl.version)
		{
			displayVersion ();
			return 0;
		}

		if (cl.invalidOpt)
		{
			cerr << cl << endl;
			return 1;
		}

		try
		{
			return cmd->execute (cl);
		}
		catch (std::invalid_argument const & ia)
		{
			cerr << "Invalid arguments passed: " << ia.what () << endl << endl;
			cerr << cl << endl;
			return 2;
		}
	}
	catch (CommandException const & ce)
	{
		std::cerr << "The command " << getErrorColor (ANSI_COLOR::BOLD) << argv[0] << " " << command
			  << getErrorColor (ANSI_COLOR::RESET) << " terminated " << getErrorColor (ANSI_COLOR::RED) << "unsuccessfully"
			  << getErrorColor (ANSI_COLOR::RESET) << " with the info:\n"
			  << ce.what () << std::endl;
		return 3;
	}
	catch (UnknownCommand const & uc)
	{
		std::cerr << "The command " << getErrorColor (ANSI_COLOR::BOLD) << argv[0] << " " << command
			  << getErrorColor (ANSI_COLOR::RESET) << " is " << getErrorColor (ANSI_COLOR::RED) << "not known"
			  << getErrorColor (ANSI_COLOR::RESET) << std::endl;
		displayHelp (argv[0], f);
		return 4;
	}
	catch (kdb::KDBException const & ce)
	{
		std::cerr << "The command " << getErrorColor (ANSI_COLOR::BOLD) << argv[0] << " " << command
			  << getErrorColor (ANSI_COLOR::RESET) << getErrorColor (ANSI_COLOR::RED) << " failed"
			  << getErrorColor (ANSI_COLOR::RESET) << " while accessing the key database with the info:\n"
			  << ce.what () << std::endl;
		return 5;
	}
	catch (std::exception const & ce)
	{
		std::cerr << "The command " << getErrorColor (ANSI_COLOR::BOLD) << argv[0] << " " << command
			  << getErrorColor (ANSI_COLOR::RESET) << " terminated " << getErrorColor (ANSI_COLOR::RED) << "unsuccessfully"
			  << getErrorColor (ANSI_COLOR::RESET) << " with the info:" << endl
			  << ce.what () << endl
			  << "Please report the issue at https://issues.libelektra.org/" << std::endl;
		return 6;
	}
	catch (...)
	{
		std::cerr << "The command " << getErrorColor (ANSI_COLOR::BOLD) << argv[0] << " " << command
			  << getErrorColor (ANSI_COLOR::RESET) << " terminated with an " << getErrorColor (ANSI_COLOR::RED)
			  << "unknown error" << getErrorColor (ANSI_COLOR::RESET) << endl
			  << "Please report the issue at https://issues.libelektra.org/" << std::endl;
		displayHelp (argv[0], f);
		return 7;
	}
}
<|endoftext|>


<commit_before>#include "Helper.h"
#include "cmath"
#include "time.h"
#include <QDir>
#include <QCoreApplication>
#include <QDateTime>

void Helper::randomInit()
{
	int seconds = time(NULL);
	int milliseconds = QTime::currentTime().msec();
	int process_id = QCoreApplication::applicationPid();
	srand(seconds + milliseconds + process_id + rand());
}

double Helper::randomNumber(double min, double max)
{
	double r = (double)rand() / (double)RAND_MAX;
	return min + r * (max - min);
}

QString Helper::randomString(int length, const QString& chars, bool init)
{
	//initialize random number generator
	if (init) randomInit();

	//create random string
	QString output;
	for (int i=0; i<length; ++i)
	{
        output.append(chars[rand() % chars.length()]);
	}
	return output;
}

QByteArray Helper::elapsedTime(QTime elapsed, bool only_seconds)
{
	//calculate minutes and seconds
	double s = elapsed.elapsed()/1000.0;
	double m = 0;
	double h = 0;
	if (!only_seconds)
	{
		m = floor(s/60.0);
		s -= 60.0 * m;
		h = floor(m/60.0);
		m -= 60.0 * h;
	}

	//create strings
	QByteArray sec = QByteArray::number(s, 'f', 3) + "s";
	QByteArray min = m==0.0 ? "" : QByteArray::number(m, 'f', 0) + "m ";
	QByteArray hours = h==0.0 ? "" : QByteArray::number(h, 'f', 0) + "h ";

	return hours + min + sec;
}

QStringList Helper::loadTextFile(QString file_name, bool trim_lines, QChar skip_header_char, bool skip_empty_lines)
{
	QStringList output;
	VersatileTextStream stream(file_name);
	while (!stream.atEnd())
	{
		QString line = stream.readLine();

		//remove newline or trim
		if (trim_lines)
		{
			line = line.trimmed();
		}
		else
		{
			while (line.endsWith('\n') || line.endsWith('\r')) line.chop(1);
		}

		//skip empty lines
		if (skip_empty_lines && line.count()==0) continue;

		//skip header lines
		if (skip_header_char!=QChar::Null && line.count()!=0 && line[0]==skip_header_char) continue;

		output.append(line);
	}

	return output;
}

void Helper::storeTextFile(QSharedPointer<QFile> file, const QStringList& lines)
{
	QTextStream stream(file.data());
	foreach(QString line, lines)
	{
		while (line.endsWith('\n') || line.endsWith('\r')) line.chop(1);
		stream << line << "\n";
	}
}

void Helper::storeTextFile(QString file_name, const QStringList& lines)
{
	storeTextFile(openFileForWriting(file_name), lines);
}

QString Helper::fileText(QString filename)
{
	QFile file(filename);
	if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
	{
		THROW(FileAccessException, "Could not open text file for reading: '" + filename + "'!");
	}

	QTextStream stream(&file); // we need a text stream to support UTF8 characters
	return stream.readAll();
}

void Helper::touchFile(QString filename)
{
	if (!QFile(filename).open(QFile::ReadWrite))
	{
		THROW(FileAccessException, "Could not open file for writing: '" + filename + "'!");
	}
}

QString Helper::tempFileName(QString extension, int length)
{
	QString name = Helper::randomString(length);
	if (extension!="")
	{
		if (!extension.startsWith(".")) name.append(".");
		name.append(extension);
	}

	return Helper::canonicalPath(QDir::tempPath() + "/" + name);
}

QStringList Helper::findFiles(const QString& directory, const QString& pattern, bool recursive)
{
	QStringList output;

	QDir dir(directory);
	if(!dir.exists()) THROW(FileAccessException, "Directory does not exist: " + directory);

	QFileInfoList file_infos = dir.entryInfoList(QStringList() << pattern, QDir::Files);
	foreach(const QFileInfo& entry, file_infos)
	{
		output.append(directory + "/" + entry.fileName());
	}

	if (recursive)
	{
		file_infos = dir.entryInfoList(QDir::AllDirs|QDir::NoDotAndDotDot);
		foreach(const QFileInfo& entry, file_infos)
		{
			output << findFiles(directory + "/" + entry.fileName(), pattern, true);
		}
	}

	return output;
}

QStringList Helper::findFolders(const QString& directory, const QString& pattern, bool recursive)
{
	QStringList output;

	QDir dir(directory);
	if(!dir.exists()) THROW(FileAccessException, "Directory does not exist: " + directory);

	QFileInfoList file_infos = dir.entryInfoList(QStringList() << pattern, QDir::Dirs|QDir::NoDotAndDotDot);
	foreach(const QFileInfo& entry, file_infos)
	{
		output.append(directory + "/" + entry.fileName());
	}
	if (recursive)
	{
		file_infos = dir.entryInfoList(QDir::AllDirs|QDir::NoDotAndDotDot);
		foreach(const QFileInfo& entry, file_infos)
		{
			output << findFolders(directory + "/" + entry.fileName(), pattern, true);
		}
	}

	return output;
}

int Helper::levenshtein(const QString& s1, const QString& s2)
{
	const int len1 = s1.size();
	const int len2 = s2.size();
	QVector<int> col(len2+1);
	QVector<int> prevCol(len2+1);

	for (int i=0; i<prevCol.size(); ++i)
	{
		prevCol[i] = i;
	}
	for (int i=0; i<len1; ++i)
	{
		col[0] = i+1;
		for (int j = 0; j < len2; j++)
		{
			col[j+1] = std::min( std::min(prevCol[1 + j] + 1, col[j] + 1), prevCol[j] + (s1[i]==s2[j] ? 0 : 1) );
		}
		col.swap(prevCol);
	}
	return prevCol[len2];
}

QString Helper::userName()
{
	return qgetenv(isWindows() ? "USERNAME" : "USER");
}

QString Helper::dateTime(QString format)
{
	if (format=="")
	{
		return QDateTime::currentDateTime().toString(Qt::ISODate);
	}

	return QDateTime::currentDateTime().toString(format);
}

bool Helper::isWritable(QString filename)
{
	if (QFile::exists(filename))
	{
		QFileInfo file_info(filename);
		if (!file_info.isFile() || !file_info.isWritable())
		{
			return false;
		}
	}
	else
	{
		QString dir = QFileInfo(filename).absolutePath();
		QFileInfo dir_info(dir);
		if (!dir_info.isDir()  || !dir_info.isWritable())
		{
			return false;
		}
	}

	return true;
}

QString Helper::canonicalPath(QString filename)
{
	//Use native separator for the current OS
	filename = QDir::toNativeSeparators(filename).trimmed();

	if (filename.isEmpty()) return filename;

	//double > single separator (except for first character in case of UNC path)
	QChar sep = QDir::separator();
	QString sep_twice = QString(sep)+sep;
	while(filename.mid(1).contains(sep_twice))
	{
		filename = filename.at(0) + filename.mid(1).replace(sep_twice, sep);
	}

	//remove "."
	QStringList parts = filename.split(sep);
	while (parts.contains("."))
	{
		int index = parts.indexOf(".");
		if (index==0) break;
		parts.removeAt(index);
	}

	//remove ".." and folder before it
	while (parts.contains(".."))
	{
		int index = parts.indexOf("..");
		if (index<=1) break;
		parts.removeAt(index);
		parts.removeAt(index-1);
	}

	return parts.join(sep);
}

bool Helper::isWindows()
{
	#ifdef _WIN32
		return true;
	#else
		return false;
	#endif
}

QSharedPointer<QFile> Helper::openFileForReading(QString file_name, bool stdin_if_empty)
{
	QSharedPointer<QFile> file(new QFile(file_name));
	if (stdin_if_empty && file_name=="")
	{
		file->open(stdin, QFile::ReadOnly | QIODevice::Text);
	}
	else if (!file->open(QFile::ReadOnly | QIODevice::Text))
	{
		THROW(FileAccessException, "Could not open file for reading: '" + file_name + "'!");
	}
	return file;
}

QSharedPointer<VersatileFile> Helper::openVersatileFileForReading(QString file_name, bool stdin_if_empty)
{
	QSharedPointer<VersatileFile> file(new VersatileFile(file_name));
	if (stdin_if_empty && file_name=="")
	{
		file->open(stdin, QFile::ReadOnly | QIODevice::Text);
	}
	else if (!file->open(QFile::ReadOnly | QIODevice::Text))
	{
		THROW(FileAccessException, "Could not open versatile file for reading: '" + file_name + "'!");
	}

	return file;
}

QSharedPointer<QFile> Helper::openFileForWriting(QString file_name, bool stdout_if_file_empty, bool append)
{
	QSharedPointer<QFile> file(new QFile(file_name));
	if (stdout_if_file_empty && file_name=="")
	{
		file->open(stdout, QFile::WriteOnly | QIODevice::Text);
	}
	else if (!file->open(QFile::WriteOnly | QIODevice::Text |(append? QFile::Append : QFile::Truncate)))
	{
		THROW(FileAccessException, "Could not open file for writing: '" + file_name + "'!");
	}

	return file;
}
<commit_msg>Skip canonicalPath method for URLs<commit_after>#include "Helper.h"
#include "cmath"
#include "time.h"
#include <QDir>
#include <QCoreApplication>
#include <QDateTime>

void Helper::randomInit()
{
	int seconds = time(NULL);
	int milliseconds = QTime::currentTime().msec();
	int process_id = QCoreApplication::applicationPid();
	srand(seconds + milliseconds + process_id + rand());
}

double Helper::randomNumber(double min, double max)
{
	double r = (double)rand() / (double)RAND_MAX;
	return min + r * (max - min);
}

QString Helper::randomString(int length, const QString& chars, bool init)
{
	//initialize random number generator
	if (init) randomInit();

	//create random string
	QString output;
	for (int i=0; i<length; ++i)
	{
        output.append(chars[rand() % chars.length()]);
	}
	return output;
}

QByteArray Helper::elapsedTime(QTime elapsed, bool only_seconds)
{
	//calculate minutes and seconds
	double s = elapsed.elapsed()/1000.0;
	double m = 0;
	double h = 0;
	if (!only_seconds)
	{
		m = floor(s/60.0);
		s -= 60.0 * m;
		h = floor(m/60.0);
		m -= 60.0 * h;
	}

	//create strings
	QByteArray sec = QByteArray::number(s, 'f', 3) + "s";
	QByteArray min = m==0.0 ? "" : QByteArray::number(m, 'f', 0) + "m ";
	QByteArray hours = h==0.0 ? "" : QByteArray::number(h, 'f', 0) + "h ";

	return hours + min + sec;
}

QStringList Helper::loadTextFile(QString file_name, bool trim_lines, QChar skip_header_char, bool skip_empty_lines)
{
	QStringList output;
	VersatileTextStream stream(file_name);
	while (!stream.atEnd())
	{
		QString line = stream.readLine();

		//remove newline or trim
		if (trim_lines)
		{
			line = line.trimmed();
		}
		else
		{
			while (line.endsWith('\n') || line.endsWith('\r')) line.chop(1);
		}

		//skip empty lines
		if (skip_empty_lines && line.count()==0) continue;

		//skip header lines
		if (skip_header_char!=QChar::Null && line.count()!=0 && line[0]==skip_header_char) continue;

		output.append(line);
	}

	return output;
}

void Helper::storeTextFile(QSharedPointer<QFile> file, const QStringList& lines)
{
	QTextStream stream(file.data());
	foreach(QString line, lines)
	{
		while (line.endsWith('\n') || line.endsWith('\r')) line.chop(1);
		stream << line << "\n";
	}
}

void Helper::storeTextFile(QString file_name, const QStringList& lines)
{
	storeTextFile(openFileForWriting(file_name), lines);
}

QString Helper::fileText(QString filename)
{
	QFile file(filename);
	if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
	{
		THROW(FileAccessException, "Could not open text file for reading: '" + filename + "'!");
	}

	QTextStream stream(&file); // we need a text stream to support UTF8 characters
	return stream.readAll();
}

void Helper::touchFile(QString filename)
{
	if (!QFile(filename).open(QFile::ReadWrite))
	{
		THROW(FileAccessException, "Could not open file for writing: '" + filename + "'!");
	}
}

QString Helper::tempFileName(QString extension, int length)
{
	QString name = Helper::randomString(length);
	if (extension!="")
	{
		if (!extension.startsWith(".")) name.append(".");
		name.append(extension);
	}

	return Helper::canonicalPath(QDir::tempPath() + "/" + name);
}

QStringList Helper::findFiles(const QString& directory, const QString& pattern, bool recursive)
{
	QStringList output;

	QDir dir(directory);
	if(!dir.exists()) THROW(FileAccessException, "Directory does not exist: " + directory);

	QFileInfoList file_infos = dir.entryInfoList(QStringList() << pattern, QDir::Files);
	foreach(const QFileInfo& entry, file_infos)
	{
		output.append(directory + "/" + entry.fileName());
	}

	if (recursive)
	{
		file_infos = dir.entryInfoList(QDir::AllDirs|QDir::NoDotAndDotDot);
		foreach(const QFileInfo& entry, file_infos)
		{
			output << findFiles(directory + "/" + entry.fileName(), pattern, true);
		}
	}

	return output;
}

QStringList Helper::findFolders(const QString& directory, const QString& pattern, bool recursive)
{
	QStringList output;

	QDir dir(directory);
	if(!dir.exists()) THROW(FileAccessException, "Directory does not exist: " + directory);

	QFileInfoList file_infos = dir.entryInfoList(QStringList() << pattern, QDir::Dirs|QDir::NoDotAndDotDot);
	foreach(const QFileInfo& entry, file_infos)
	{
		output.append(directory + "/" + entry.fileName());
	}
	if (recursive)
	{
		file_infos = dir.entryInfoList(QDir::AllDirs|QDir::NoDotAndDotDot);
		foreach(const QFileInfo& entry, file_infos)
		{
			output << findFolders(directory + "/" + entry.fileName(), pattern, true);
		}
	}

	return output;
}

int Helper::levenshtein(const QString& s1, const QString& s2)
{
	const int len1 = s1.size();
	const int len2 = s2.size();
	QVector<int> col(len2+1);
	QVector<int> prevCol(len2+1);

	for (int i=0; i<prevCol.size(); ++i)
	{
		prevCol[i] = i;
	}
	for (int i=0; i<len1; ++i)
	{
		col[0] = i+1;
		for (int j = 0; j < len2; j++)
		{
			col[j+1] = std::min( std::min(prevCol[1 + j] + 1, col[j] + 1), prevCol[j] + (s1[i]==s2[j] ? 0 : 1) );
		}
		col.swap(prevCol);
	}
	return prevCol[len2];
}

QString Helper::userName()
{
	return qgetenv(isWindows() ? "USERNAME" : "USER");
}

QString Helper::dateTime(QString format)
{
	if (format=="")
	{
		return QDateTime::currentDateTime().toString(Qt::ISODate);
	}

	return QDateTime::currentDateTime().toString(format);
}

bool Helper::isWritable(QString filename)
{
	if (QFile::exists(filename))
	{
		QFileInfo file_info(filename);
		if (!file_info.isFile() || !file_info.isWritable())
		{
			return false;
		}
	}
	else
	{
		QString dir = QFileInfo(filename).absolutePath();
		QFileInfo dir_info(dir);
		if (!dir_info.isDir()  || !dir_info.isWritable())
		{
			return false;
		}
	}

	return true;
}

QString Helper::canonicalPath(QString filename)
{
	if (filename.startsWith("http")) return filename;

	//Use native separator for the current OS
	filename = QDir::toNativeSeparators(filename).trimmed();

	if (filename.isEmpty()) return filename;

	//double > single separator (except for first character in case of UNC path)
	QChar sep = QDir::separator();
	QString sep_twice = QString(sep)+sep;
	while(filename.mid(1).contains(sep_twice))
	{
		filename = filename.at(0) + filename.mid(1).replace(sep_twice, sep);
	}

	//remove "."
	QStringList parts = filename.split(sep);
	while (parts.contains("."))
	{
		int index = parts.indexOf(".");
		if (index==0) break;
		parts.removeAt(index);
	}

	//remove ".." and folder before it
	while (parts.contains(".."))
	{
		int index = parts.indexOf("..");
		if (index<=1) break;
		parts.removeAt(index);
		parts.removeAt(index-1);
	}

	return parts.join(sep);
}

bool Helper::isWindows()
{
	#ifdef _WIN32
		return true;
	#else
		return false;
	#endif
}

QSharedPointer<QFile> Helper::openFileForReading(QString file_name, bool stdin_if_empty)
{
	QSharedPointer<QFile> file(new QFile(file_name));
	if (stdin_if_empty && file_name=="")
	{
		file->open(stdin, QFile::ReadOnly | QIODevice::Text);
	}
	else if (!file->open(QFile::ReadOnly | QIODevice::Text))
	{
		THROW(FileAccessException, "Could not open file for reading: '" + file_name + "'!");
	}
	return file;
}

QSharedPointer<VersatileFile> Helper::openVersatileFileForReading(QString file_name, bool stdin_if_empty)
{
	QSharedPointer<VersatileFile> file(new VersatileFile(file_name));
	if (stdin_if_empty && file_name=="")
	{
		file->open(stdin, QFile::ReadOnly | QIODevice::Text);
	}
	else if (!file->open(QFile::ReadOnly | QIODevice::Text))
	{
		THROW(FileAccessException, "Could not open versatile file for reading: '" + file_name + "'!");
	}

	return file;
}

QSharedPointer<QFile> Helper::openFileForWriting(QString file_name, bool stdout_if_file_empty, bool append)
{
	QSharedPointer<QFile> file(new QFile(file_name));
	if (stdout_if_file_empty && file_name=="")
	{
		file->open(stdout, QFile::WriteOnly | QIODevice::Text);
	}
	else if (!file->open(QFile::WriteOnly | QIODevice::Text |(append? QFile::Append : QFile::Truncate)))
	{
		THROW(FileAccessException, "Could not open file for writing: '" + file_name + "'!");
	}

	return file;
}
<|endoftext|>


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

#ifndef ZORBA_UTF8_UTIL_TCC
#define ZORBA_UTF8_UTIL_TCC

#ifndef ZORBA_UTF8_UTIL_H
#error "This file is not meant to be included directly."
#endif /* ZORBA_UTF8_UTIL_H */

#include <cctype>

#include "util/string/string_traits.h"
#include "zorbaerrors/error_manager.h"

namespace zorba {
namespace utf8 {

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

template<class StringType> back_html_uri_insert_iterator<StringType>&
back_html_uri_insert_iterator<StringType>::operator=( value_type c ) {
  char const dec2hex[] = "0123456789ABCDEF";
  unsigned u = c & 0xFF;
  if ( !isprint( u ) ) {
    utf8::encoded_char_type ec;
    utf8::size_type const bytes = utf8::encode( c, ec );
    for ( size_type i = 0; i < bytes; ++i ) {
      u = ec[i] & 0xFF;
      buf_[1] = dec2hex[ u >> 4 ];
      buf_[2] = dec2hex[ u & 0x0F ];
      this->container->append( buf_, 3 );
    }
  } else {
    this->container->push_back( c );
  }
  return *this;
}

template<class StringType> back_iri_insert_iterator<StringType>&
back_iri_insert_iterator<StringType>::operator=( value_type c ) {
  char const dec2hex[] = "0123456789ABCDEF";
  unsigned u = c & 0xFF;
  if ( unicode::is_ucschar( c ) || unicode::is_iprivate( c ) ||
       unicode::is_invalid_in_iri( c ) ) {
    utf8::encoded_char_type ec;
    utf8::size_type const bytes = utf8::encode( c, ec );
    for ( size_type i = 0; i < bytes; ++i ) {
      u = ec[i] & 0xFF;;
      buf_[1] = dec2hex[ u >> 4 ];
      buf_[2] = dec2hex[ u & 0x0F ];
      this->container->append( buf_, 3 );
    }
  } else {
    this->container->push_back( c );
  }
  return *this;
}

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

template<class OctetIterator>
unicode::code_point next_char( OctetIterator &i ) {
  unicode::code_point c = *i & 0xFFu;   // prevents sign-extension
  if ( c < 0x80 )                       // special-case ASCII
    ++i;
  else {
    size_type const len = char_length( c );
    unsigned m = (0x7F >> len) & 0x1F;  // mask
    c = unicode::code_point( 0 );
    bool b = false;                     // continuation byte?
    switch ( len ) {
      case 6: c |= ((assert_valid_byte( *i, !b++ ) & m) << 30); ++i; m = 0x3F;
      case 5: c |= ((assert_valid_byte( *i, !b++ ) & m) << 24); ++i; m = 0x3F;
      case 4: c |= ((assert_valid_byte( *i, !b++ ) & m) << 18); ++i; m = 0x3F;
      case 3: c |= ((assert_valid_byte( *i, !b++ ) & m) << 12); ++i; m = 0x3F;
      case 2: c |= ((assert_valid_byte( *i, !b++ ) & m) <<  6); ++i;
              c |=  (assert_valid_byte( *i, !b   ) & 0x3F)    ; ++i;
    }
  }
  if ( !unicode::is_valid( c ) )
    ZORBA_ERROR( FOCH0001 );
  return c;
}

template<class OctetIterator>
unicode::code_point prev_char( OctetIterator &oi ) {
  while ( !is_start_byte( *--oi ) ) ;
  OctetIterator temp( oi );
  return next_char( temp );
}

#ifndef ZORBA_NO_UNICODE

template<class InputStringType,class OutputStringType>
bool normalize( InputStringType const &in, unicode::normalization::type n,
                OutputStringType *out ) {
  unicode::string u_in;
  if ( !unicode::to_string( in, &u_in ) )
    return false;
  unicode::string u_out;
  if ( !unicode::normalize( u_in, n, &u_out ) )
    return false;
  storage_type *temp;
  size_type temp_len;
  if ( !utf8::to_string( u_out.getBuffer(), u_out.length(), &temp, &temp_len ) )
    return false;
  out->assign( temp, temp_len );
  return true;
}

template<class InputStringType,class OutputStringType>
void strip_diacritics( InputStringType const &in, OutputStringType *out ) {
  InputStringType in_normalized;
  normalize( in, unicode::normalization::NFKD, &in_normalized );
  out->clear();
  out->reserve( in_normalized.size() );
  std::copy(
    in_normalized.begin(), in_normalized.end(),
    ascii::back_ascii_inserter( *out )
  );
}

template<class StringType>
bool to_string( unicode::char_type const *in, size_type in_len,
                StringType *out ) {
  storage_type *temp;
  size_type temp_len;
  if ( to_string( in, in_len, &temp, &temp_len ) ) {
    out->assign(temp, temp_len);
    if ( !string_traits<StringType>::takes_pointer_ownership )
      delete[] temp;
    return true;
  }
  return false;
}

#ifndef WIN32
template<class StringType>
bool to_string( wchar_t const *in, size_type in_len, StringType *out ) {
  storage_type *temp;
  size_type temp_len;
  if ( utf8::to_string( in, in_len, &temp, &temp_len ) ) {
    out->assign( temp, temp_len );
    if ( !string_traits<StringType>::takes_pointer_ownership )
      delete[] temp;
    return true;
  }
  return false;
}
#endif /* WIN32 */

#endif /* ZORBA_NO_UNICODE */

template<class InputStringType,class OutputStringType>
void to_lower( InputStringType const &in, OutputStringType *out ) {
  typename utf8_stringify<InputStringType const>::type const u_in( in );
  typename utf8_stringify<OutputStringType>::type u_out( *out );
  out->clear(); // TODO: should this be here?
  std::transform(
    u_in.begin(), u_in.end(), std::back_inserter( u_out ), unicode::to_lower
  );
}

template<class InputStringType,class OutputStringType>
void to_upper( InputStringType const &in, OutputStringType *out ) {
  typename utf8_stringify<InputStringType const>::type const u_in( in );
  typename utf8_stringify<OutputStringType>::type u_out( *out );
  out->clear(); // TODO: should this be here?
  std::transform(
    u_in.begin(), u_in.end(), std::back_inserter( u_out ), unicode::to_upper
  );
}

} // namespace utf8
} // namespace zorba

#endif /* ZORBA_UTF8_UTIL_TCC */

/*
 * Local variables:
 * mode: c++
 * End:
 */
/* vim:set et sw=2 ts=2: */
<commit_msg>Fixed clang warning.<commit_after>/*
 * Copyright 2006-2008 The FLWOR Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ZORBA_UTF8_UTIL_TCC
#define ZORBA_UTF8_UTIL_TCC

#ifndef ZORBA_UTF8_UTIL_H
#error "This file is not meant to be included directly."
#endif /* ZORBA_UTF8_UTIL_H */

#include <cctype>

#include "util/string/string_traits.h"
#include "zorbaerrors/error_manager.h"

namespace zorba {
namespace utf8 {

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

template<class StringType> back_html_uri_insert_iterator<StringType>&
back_html_uri_insert_iterator<StringType>::operator=( value_type c ) {
  char const dec2hex[] = "0123456789ABCDEF";
  unsigned u = c & 0xFF;
  if ( !isprint( u ) ) {
    utf8::encoded_char_type ec;
    utf8::size_type const bytes = utf8::encode( c, ec );
    for ( size_type i = 0; i < bytes; ++i ) {
      u = ec[i] & 0xFF;
      buf_[1] = dec2hex[ u >> 4 ];
      buf_[2] = dec2hex[ u & 0x0F ];
      this->container->append( buf_, 3 );
    }
  } else {
    this->container->push_back( c );
  }
  return *this;
}

template<class StringType> back_iri_insert_iterator<StringType>&
back_iri_insert_iterator<StringType>::operator=( value_type c ) {
  char const dec2hex[] = "0123456789ABCDEF";
  unsigned u = c & 0xFF;
  if ( unicode::is_ucschar( c ) || unicode::is_iprivate( c ) ||
       unicode::is_invalid_in_iri( c ) ) {
    utf8::encoded_char_type ec;
    utf8::size_type const bytes = utf8::encode( c, ec );
    for ( size_type i = 0; i < bytes; ++i ) {
      u = ec[i] & 0xFF;;
      buf_[1] = dec2hex[ u >> 4 ];
      buf_[2] = dec2hex[ u & 0x0F ];
      this->container->append( buf_, 3 );
    }
  } else {
    this->container->push_back( c );
  }
  return *this;
}

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

template<class OctetIterator>
unicode::code_point next_char( OctetIterator &i ) {
  unicode::code_point c = *i & 0xFFu;   // prevents sign-extension
  if ( c < 0x80 )                       // special-case ASCII
    ++i;
  else {
    size_type const len = char_length( c );
    unsigned m = (0x7F >> len) & 0x1F;  // mask
    c = unicode::code_point( 0 );
    int b = false;                      // continuation byte?
    switch ( len ) {
      case 6: c |= ((assert_valid_byte( *i, !b++ ) & m) << 30); ++i; m = 0x3F;
      case 5: c |= ((assert_valid_byte( *i, !b++ ) & m) << 24); ++i; m = 0x3F;
      case 4: c |= ((assert_valid_byte( *i, !b++ ) & m) << 18); ++i; m = 0x3F;
      case 3: c |= ((assert_valid_byte( *i, !b++ ) & m) << 12); ++i; m = 0x3F;
      case 2: c |= ((assert_valid_byte( *i, !b++ ) & m) <<  6); ++i;
              c |=  (assert_valid_byte( *i, !b   ) & 0x3F)    ; ++i;
    }
  }
  if ( !unicode::is_valid( c ) )
    ZORBA_ERROR( FOCH0001 );
  return c;
}

template<class OctetIterator>
unicode::code_point prev_char( OctetIterator &oi ) {
  while ( !is_start_byte( *--oi ) ) ;
  OctetIterator temp( oi );
  return next_char( temp );
}

#ifndef ZORBA_NO_UNICODE

template<class InputStringType,class OutputStringType>
bool normalize( InputStringType const &in, unicode::normalization::type n,
                OutputStringType *out ) {
  unicode::string u_in;
  if ( !unicode::to_string( in, &u_in ) )
    return false;
  unicode::string u_out;
  if ( !unicode::normalize( u_in, n, &u_out ) )
    return false;
  storage_type *temp;
  size_type temp_len;
  if ( !utf8::to_string( u_out.getBuffer(), u_out.length(), &temp, &temp_len ) )
    return false;
  out->assign( temp, temp_len );
  return true;
}

template<class InputStringType,class OutputStringType>
void strip_diacritics( InputStringType const &in, OutputStringType *out ) {
  InputStringType in_normalized;
  normalize( in, unicode::normalization::NFKD, &in_normalized );
  out->clear();
  out->reserve( in_normalized.size() );
  std::copy(
    in_normalized.begin(), in_normalized.end(),
    ascii::back_ascii_inserter( *out )
  );
}

template<class StringType>
bool to_string( unicode::char_type const *in, size_type in_len,
                StringType *out ) {
  storage_type *temp;
  size_type temp_len;
  if ( to_string( in, in_len, &temp, &temp_len ) ) {
    out->assign(temp, temp_len);
    if ( !string_traits<StringType>::takes_pointer_ownership )
      delete[] temp;
    return true;
  }
  return false;
}

#ifndef WIN32
template<class StringType>
bool to_string( wchar_t const *in, size_type in_len, StringType *out ) {
  storage_type *temp;
  size_type temp_len;
  if ( utf8::to_string( in, in_len, &temp, &temp_len ) ) {
    out->assign( temp, temp_len );
    if ( !string_traits<StringType>::takes_pointer_ownership )
      delete[] temp;
    return true;
  }
  return false;
}
#endif /* WIN32 */

#endif /* ZORBA_NO_UNICODE */

template<class InputStringType,class OutputStringType>
void to_lower( InputStringType const &in, OutputStringType *out ) {
  typename utf8_stringify<InputStringType const>::type const u_in( in );
  typename utf8_stringify<OutputStringType>::type u_out( *out );
  out->clear(); // TODO: should this be here?
  std::transform(
    u_in.begin(), u_in.end(), std::back_inserter( u_out ), unicode::to_lower
  );
}

template<class InputStringType,class OutputStringType>
void to_upper( InputStringType const &in, OutputStringType *out ) {
  typename utf8_stringify<InputStringType const>::type const u_in( in );
  typename utf8_stringify<OutputStringType>::type u_out( *out );
  out->clear(); // TODO: should this be here?
  std::transform(
    u_in.begin(), u_in.end(), std::back_inserter( u_out ), unicode::to_upper
  );
}

} // namespace utf8
} // namespace zorba

#endif /* ZORBA_UTF8_UTIL_TCC */

/*
 * Local variables:
 * mode: c++
 * End:
 */
/* vim:set et sw=2 ts=2: */
<|endoftext|>


<commit_before>//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.

#include <limits>
#include <queue> //Priority queue to prioritise removing unimportant vertices.

#include "Simplify.h"

namespace cura
{

Simplify::Simplify(const coord_t max_resolution, const coord_t max_deviation, const coord_t max_area_deviation)
    : max_resolution(max_resolution)
    , max_deviation(max_deviation)
    , max_area_deviation(max_area_deviation)
{}

Simplify::Simplify(const Settings& settings)
    : max_resolution(settings.get<coord_t>("meshfix_maximum_resolution"))
    , max_deviation(settings.get<coord_t>("meshfix_maximum_deviation"))
    , max_area_deviation(settings.get<coord_t>("meshfix_maximum_area_deviation"))
{}

Polygon Simplify::polygon(const Polygon& polygon)
{
    constexpr bool is_closed = true;
    return simplify(polygon, is_closed);
}

ExtrusionLine Simplify::polygon(const ExtrusionLine& polygon)
{
    constexpr bool is_closed = true;
    return simplify(polygon, is_closed);
}

Polygon Simplify::polyline(const Polygon& polyline)
{
    constexpr bool is_closed = false;
    return simplify(polyline, is_closed);
}

size_t Simplify::nextNotDeleted(size_t index, const std::vector<bool>& to_delete) const
{
    const size_t size = to_delete.size();
    for(index = (index + 1) % size; to_delete[index]; index = (index + 1) % size); //Changes the index variable in-place until we found one that is not deleted.
    return index;
}

size_t Simplify::previousNotDeleted(size_t index, const std::vector<bool>& to_delete) const
{
    const size_t size = to_delete.size();
    for(index = (index + size - 1) % size; to_delete[index]; index = (index + size - 1) % size); //Changes the index variable in-place until we found one that is not deleted.
    return index;
}

void Simplify::appendVertex(Polygon& polygon, const Point& vertex) const
{
    polygon.add(vertex);
}

void Simplify::appendVertex(ExtrusionLine& extrusion_line, const ExtrusionJunction& vertex) const
{
    extrusion_line.junctions.push_back(vertex);
}

Point Simplify::getPosition(const Point& vertex) const
{
    return vertex;
}

Point Simplify::getPosition(const ExtrusionJunction& vertex) const
{
    return vertex.p;
}

Point Simplify::createIntersection(const Point& before, const Point intersection, const Point& after) const
{
    return intersection;
}

ExtrusionJunction Simplify::createIntersection(const ExtrusionJunction& before, const Point intersection, const ExtrusionJunction& after) const
{
    //Average the extrusion width of the line.
    //More correct would be to see where along the line the intersection occurs with a projection or something.
    //But these details are so small, and this solution is so much quicker and simpler.
    return ExtrusionJunction(intersection, (before.w + after.w) / 2, before.perimeter_index);
}

coord_t Simplify::getAreaDeviation(const Point& before, const Point& vertex, const Point& after) const
{
    return 0; //Fixed-width polygons don't have any deviation.
}

coord_t Simplify::getAreaDeviation(const ExtrusionJunction& before, const ExtrusionJunction& vertex, const ExtrusionJunction& after) const
{
    /*
     * A             B                          C              A                                        C
     * ---------------                                         **************
     * |             |                                         ------------------------------------------
     * |             |--------------------------|  B removed   |            |***************************|
     * |             |                          |  --------->  |            |                           |
     * |             |--------------------------|              |            |***************************|
     * |             |                                         ------------------------------------------
     * ---------------             ^                           **************
     *       ^                B.w + C.w / 2                                       ^
     *  A.w + B.w / 2                                               new_width = weighted_average_width
     *
     *
     * ******** denote the total extrusion area deviation error in the consecutive segments as a result of using the
     * weighted-average width for the entire extrusion line.
     *
     * */
    const coord_t ab_length = vSize(vertex - before);
    const coord_t bc_length = vSize(after - vertex);
    const coord_t width_diff = std::max(std::abs(vertex.w - before.w), std::abs(after.w - vertex.w));
    if (width_diff > 1)
    {
        // Adjust the width only if there is a difference, or else the rounding errors may produce the wrong
        // weighted average value.
        const coord_t ab_weight = (before.w + vertex.w) / 2;
        const coord_t bc_weight = (vertex.w + after.w) / 2;
        const coord_t weighted_average_width = (ab_length * ab_weight + bc_length * bc_weight) / vSize(after - before);
        return std::abs(ab_weight - weighted_average_width) * ab_length + std::abs(bc_weight - weighted_average_width) * bc_length;
    }
    else
    {
        // If the width difference is very small, then select the width of the segment that is longer
        return ab_length > bc_length ? width_diff * bc_length : width_diff * ab_length;
    }
}

}<commit_msg>Also implement simplification for polylines of variable width<commit_after>//Copyright (c) 2022 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.

#include <limits>
#include <queue> //Priority queue to prioritise removing unimportant vertices.

#include "Simplify.h"

namespace cura
{

Simplify::Simplify(const coord_t max_resolution, const coord_t max_deviation, const coord_t max_area_deviation)
    : max_resolution(max_resolution)
    , max_deviation(max_deviation)
    , max_area_deviation(max_area_deviation)
{}

Simplify::Simplify(const Settings& settings)
    : max_resolution(settings.get<coord_t>("meshfix_maximum_resolution"))
    , max_deviation(settings.get<coord_t>("meshfix_maximum_deviation"))
    , max_area_deviation(settings.get<coord_t>("meshfix_maximum_area_deviation"))
{}

Polygon Simplify::polygon(const Polygon& polygon)
{
    constexpr bool is_closed = true;
    return simplify(polygon, is_closed);
}

ExtrusionLine Simplify::polygon(const ExtrusionLine& polygon)
{
    constexpr bool is_closed = true;
    return simplify(polygon, is_closed);
}

Polygon Simplify::polyline(const Polygon& polyline)
{
    constexpr bool is_closed = false;
    return simplify(polyline, is_closed);
}

ExtrusionLine Simplify::polyline(const ExtrusionLine& polyline)
{
    constexpr bool is_closed = false;
    return simplify(polyline, is_closed);
}

size_t Simplify::nextNotDeleted(size_t index, const std::vector<bool>& to_delete) const
{
    const size_t size = to_delete.size();
    for(index = (index + 1) % size; to_delete[index]; index = (index + 1) % size); //Changes the index variable in-place until we found one that is not deleted.
    return index;
}

size_t Simplify::previousNotDeleted(size_t index, const std::vector<bool>& to_delete) const
{
    const size_t size = to_delete.size();
    for(index = (index + size - 1) % size; to_delete[index]; index = (index + size - 1) % size); //Changes the index variable in-place until we found one that is not deleted.
    return index;
}

void Simplify::appendVertex(Polygon& polygon, const Point& vertex) const
{
    polygon.add(vertex);
}

void Simplify::appendVertex(ExtrusionLine& extrusion_line, const ExtrusionJunction& vertex) const
{
    extrusion_line.junctions.push_back(vertex);
}

Point Simplify::getPosition(const Point& vertex) const
{
    return vertex;
}

Point Simplify::getPosition(const ExtrusionJunction& vertex) const
{
    return vertex.p;
}

Point Simplify::createIntersection(const Point& before, const Point intersection, const Point& after) const
{
    return intersection;
}

ExtrusionJunction Simplify::createIntersection(const ExtrusionJunction& before, const Point intersection, const ExtrusionJunction& after) const
{
    //Average the extrusion width of the line.
    //More correct would be to see where along the line the intersection occurs with a projection or something.
    //But these details are so small, and this solution is so much quicker and simpler.
    return ExtrusionJunction(intersection, (before.w + after.w) / 2, before.perimeter_index);
}

coord_t Simplify::getAreaDeviation(const Point& before, const Point& vertex, const Point& after) const
{
    return 0; //Fixed-width polygons don't have any deviation.
}

coord_t Simplify::getAreaDeviation(const ExtrusionJunction& before, const ExtrusionJunction& vertex, const ExtrusionJunction& after) const
{
    /*
     * A             B                          C              A                                        C
     * ---------------                                         **************
     * |             |                                         ------------------------------------------
     * |             |--------------------------|  B removed   |            |***************************|
     * |             |                          |  --------->  |            |                           |
     * |             |--------------------------|              |            |***************************|
     * |             |                                         ------------------------------------------
     * ---------------             ^                           **************
     *       ^                B.w + C.w / 2                                       ^
     *  A.w + B.w / 2                                               new_width = weighted_average_width
     *
     *
     * ******** denote the total extrusion area deviation error in the consecutive segments as a result of using the
     * weighted-average width for the entire extrusion line.
     *
     * */
    const coord_t ab_length = vSize(vertex - before);
    const coord_t bc_length = vSize(after - vertex);
    const coord_t width_diff = std::max(std::abs(vertex.w - before.w), std::abs(after.w - vertex.w));
    if (width_diff > 1)
    {
        // Adjust the width only if there is a difference, or else the rounding errors may produce the wrong
        // weighted average value.
        const coord_t ab_weight = (before.w + vertex.w) / 2;
        const coord_t bc_weight = (vertex.w + after.w) / 2;
        const coord_t weighted_average_width = (ab_length * ab_weight + bc_length * bc_weight) / vSize(after - before);
        return std::abs(ab_weight - weighted_average_width) * ab_length + std::abs(bc_weight - weighted_average_width) * bc_length;
    }
    else
    {
        // If the width difference is very small, then select the width of the segment that is longer
        return ab_length > bc_length ? width_diff * bc_length : width_diff * ab_length;
    }
}

}<|endoftext|>


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

#include "gridview.h"

#include "../global.h"
#include "../common.h"

#include "widgets/clickablewidget.h"

#include <QFile>
#include <QFileInfo>
#include <QGridLayout>
#include <QKeyEvent>
#include <QLabel>
#include <QScrollArea>
#include <QScrollBar>

GridView::GridView(QWidget *parent) : QScrollArea(parent)
{
    this->parent = parent;

    setObjectName("gridView");
    setStyleSheet("#gridView { border: none; }");
    setBackgroundRole(QPalette::Dark);
    setAlignment(Qt::AlignHCenter);
    setHidden(true);

    setGridBackground();

    gridWidget = new QWidget(this);
    gridWidget->setObjectName("gridWidget");
    setWidget(gridWidget);

    gridLayout = new QGridLayout(gridWidget);
    gridLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
    gridLayout->setRowMinimumHeight(0, 10);

    gridWidget->setLayout(gridLayout);

    gridCurrent = false;
    currentGridRom = 0;
}

void GridView::addToGridView(Rom *currentRom, int count, bool ddEnabled)
{
    if (ddEnabled) // Add place for "No Cart" entry
        count++;

    ClickableWidget *gameGridItem = new ClickableWidget(gridWidget);
    gameGridItem->setMinimumWidth(getGridSize("width"));
    gameGridItem->setMaximumWidth(getGridSize("width"));
    gameGridItem->setGraphicsEffect(getShadow(false));
    gameGridItem->setContextMenuPolicy(Qt::CustomContextMenu);

    //Assign ROM data to widget for use in click events
    gameGridItem->setProperty("fileName", currentRom->fileName);
    gameGridItem->setProperty("directory", currentRom->directory);
    if (currentRom->goodName == getTranslation("Unknown ROM") ||
        currentRom->goodName == getTranslation("Requires catalog file"))
        gameGridItem->setProperty("search", currentRom->internalName);
    else
        gameGridItem->setProperty("search", currentRom->goodName);
    gameGridItem->setProperty("romMD5", currentRom->romMD5);
    gameGridItem->setProperty("zipFile", currentRom->zipFile);

    QGridLayout *gameGridLayout = new QGridLayout(gameGridItem);
    gameGridLayout->setColumnStretch(0, 1);
    gameGridLayout->setColumnStretch(3, 1);
    gameGridLayout->setRowMinimumHeight(1, getImageSize("Grid").height());

    QLabel *gridImageLabel = new QLabel(gameGridItem);
    gridImageLabel->setMinimumHeight(getImageSize("Grid").height());
    gridImageLabel->setMinimumWidth(getImageSize("Grid").width());
    QPixmap image;

    if (currentRom->imageExists) {
        //Use uniform aspect ratio to account for fluctuations in TheGamesDB box art
        Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio;

        //Don't warp aspect ratio though if image is too far away from standard size (JP box art)
        float aspectRatio = float(currentRom->image.width()) / currentRom->image.height();

        if (aspectRatio < 1.1 || aspectRatio > 1.8)
            aspectRatioMode = Qt::KeepAspectRatio;

        image = currentRom->image.scaled(getImageSize("Grid"), aspectRatioMode, Qt::SmoothTransformation);
    } else {
        if (ddEnabled && count == 0)
            image = QPixmap(":/images/no-cart.png").scaled(getImageSize("Grid"), Qt::IgnoreAspectRatio,
                                                             Qt::SmoothTransformation);
        else
            image = QPixmap(":/images/not-found.png").scaled(getImageSize("Grid"), Qt::IgnoreAspectRatio,
                                                             Qt::SmoothTransformation);
    }

    gridImageLabel->setPixmap(image);
    gridImageLabel->setAlignment(Qt::AlignCenter);
    gameGridLayout->addWidget(gridImageLabel, 1, 1);

    if (SETTINGS.value("Grid/label","true") == "true") {
        QLabel *gridTextLabel = new QLabel(gameGridItem);

        //Don't allow label to be wider than image
        gridTextLabel->setMaximumWidth(getImageSize("Grid").width());

        QString text = "";
        QString labelText = SETTINGS.value("Grid/labeltext","Filename").toString();

        text = getRomInfo(labelText, currentRom);

        if (ddEnabled && count == 0)
            text = tr("No Cart");

        gridTextLabel->setText(text);

        QString textHex = getColor(SETTINGS.value("Grid/labelcolor","White").toString()).name();
        int fontSize = getGridSize("font");

        gridTextLabel->setStyleSheet("QLabel { font-weight: bold; color: " + textHex + "; font-size: "
                                     + QString::number(fontSize) + "px; }");
        gridTextLabel->setWordWrap(true);
        gridTextLabel->setAlignment(Qt::AlignHCenter | Qt::AlignTop);

        gameGridLayout->addWidget(gridTextLabel, 2, 1);
    }

    gameGridItem->setLayout(gameGridLayout);

    gameGridItem->setMinimumHeight(gameGridItem->sizeHint().height());

    int columnCount;
    if (SETTINGS.value("Grid/autocolumns","true").toString() == "true")
        columnCount = viewport()->width() / (getGridSize("width") + 10);
    else
        columnCount = SETTINGS.value("Grid/columncount", "4").toInt();

    if (columnCount == 0) columnCount = 1;

    gridLayout->addWidget(gameGridItem, count / columnCount + 1, count % columnCount + 1);
    gridWidget->adjustSize();

    connect(gameGridItem, SIGNAL(singleClicked(QWidget*)), this, SLOT(highlightGridWidget(QWidget*)));
    connect(gameGridItem, SIGNAL(doubleClicked(QWidget*)), parent, SLOT(launchRomFromWidget(QWidget*)));
    connect(gameGridItem, SIGNAL(arrowPressed(QWidget*, QString)), this, SLOT(selectNextRom(QWidget*, QString)));
    connect(gameGridItem, SIGNAL(enterPressed(QWidget*)), parent, SLOT(launchRomFromWidget(QWidget*)));
    connect(gameGridItem, SIGNAL(customContextMenuRequested(const QPoint &)), parent, SLOT(showRomMenu(const QPoint &)));
}

int GridView::getCurrentRom()
{
    return currentGridRom;
}

QString GridView::getCurrentRomInfo(QString infoName)
{
    const char *property = infoName.toUtf8().constData();

    if (gridLayout->count() > currentGridRom)
        return gridLayout->itemAt(currentGridRom)->widget()->property(property).toString();
    return "";
}

QWidget *GridView::getCurrentRomWidget()
{
    return gridLayout->itemAt(currentGridRom)->widget();
}

bool GridView::hasSelectedRom()
{
    return gridCurrent;
}

void GridView::highlightGridWidget(QWidget *current)
{
    current->setFocus();

    //Set all to inactive shadow
    QLayoutItem *gridItem;
    for (int item = 0; (gridItem = gridLayout->itemAt(item)) != NULL; item++)
    {
        gridItem->widget()->setGraphicsEffect(getShadow(false));

        if (gridItem->widget() == current)
            currentGridRom = item;
    }

    //Set current to active shadow
    current->setGraphicsEffect(getShadow(true));

    gridCurrent = true;
    emit gridItemSelected(true);
}

void GridView::keyPressEvent(QKeyEvent *event)
{
    if ((event->key() == Qt::Key_Down || event->key() == Qt::Key_Right) && gridLayout->count() > 0) {
        highlightGridWidget(gridLayout->itemAt(0)->widget());
        ensureWidgetVisible(gridLayout->itemAt(0)->widget());
    } else
        QScrollArea::keyPressEvent(event);
}

void GridView::resetView()
{
    QLayoutItem *gridItem;
    while ((gridItem = gridLayout->takeAt(0)) != NULL)
    {
        delete gridItem->widget();
        delete gridItem;
    }

    gridCurrent = false;
}

void GridView::resizeEvent(QResizeEvent *event)
{
    int check = event->size().width() / (getGridSize("width") + 10);
    bool autoAdjustColumns = SETTINGS.value("Grid/autocolumns","true").toString() == "true";

    if (autoAdjustColumns)
        setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    else
        setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);

    if (autoAdjustColumns && check != autoColumnCount && check != 0) {
        autoColumnCount = check;
        updateGridColumns(event->size().width());
    } else
        QScrollArea::resizeEvent(event);
}

void GridView::saveGridPosition()
{
    positionx = horizontalScrollBar()->value();
    positiony = verticalScrollBar()->value();

    if (gridCurrent)
        savedGridRom = currentGridRom;
    else
        savedGridRom = -1;
    savedGridRomFilename = getCurrentRomInfo("fileName");
}

void GridView::selectNextRom(QWidget* current, QString keypress)
{
    int columnCount;
    if (SETTINGS.value("Grid/autocolumns","true").toString() == "true")
        columnCount = autoColumnCount;
    else
        columnCount = SETTINGS.value("Grid/columncount", "4").toInt();

    int offset = 0;
    if (keypress == "UP")
        offset = columnCount * -1;
    else if (keypress == "DOWN")
        offset = columnCount;
    else if (keypress == "RIGHT")
        offset = 1;
    else if (keypress == "LEFT")
        offset = -1;

    QLayoutItem *gridItem;
    for (int item = 0; (gridItem = gridLayout->itemAt(item)) != NULL; item++)
    {
        if (gridItem->widget() == current && item + offset >= 0 && gridLayout->itemAt(item + offset) != NULL) {
            ensureWidgetVisible(gridLayout->itemAt(item + offset)->widget());
            highlightGridWidget(gridLayout->itemAt(item + offset)->widget());
        }
    }
}

void GridView::setGridBackground()
{
    QString theme = SETTINGS.value("Grid/theme", "Normal").toString();
    if (theme == "Light")
        setStyleSheet("#gridView { border: none; background: #FFF; } #gridWidget { background: transparent; }");
    else if (theme == "Dark")
        setStyleSheet("#gridView { border: none; background: #222; } #gridWidget { background: transparent; }");
    else
        setStyleSheet("#gridView { border: none; }");

    QString background = SETTINGS.value("Grid/background", "").toString();
    if (background != "") {
        QFile backgroundFile(background);

        if (backgroundFile.exists() && !QFileInfo(backgroundFile).isDir())
            setStyleSheet(QString()
                + "#gridView { "
                    + "border: none; "
                    + "background: url(" + background + "); "
                    + "background-attachment: fixed; "
                    + "background-position: top center; "
                + "} #gridWidget { background: transparent; } "
            );
    }
}

void GridView::setGridPosition()
{
    horizontalScrollBar()->setValue(positionx);
    verticalScrollBar()->setValue(positiony);

    //Restore selected ROM if it is in the same position
    if (savedGridRom != -1 && gridLayout->count() > savedGridRom) {
        QWidget *checkWidget = gridLayout->itemAt(savedGridRom)->widget();
        if (checkWidget->property("fileName").toString() == savedGridRomFilename)
            highlightGridWidget(checkWidget);
    }
}

void GridView::updateGridColumns(int width)
{
    int columnCount = width / (getGridSize("width") + 10);

    int gridCount = gridLayout->count();
    QList<QWidget*> gridItems;
    for (int count = 0; count < gridCount; count++)
        gridItems << gridLayout->takeAt(0)->widget();

    int count = 0;
    foreach(QWidget *gridItem, gridItems)
    {
        gridLayout->addWidget(gridItem, count / columnCount + 1, count % columnCount + 1);
        count++;
    }

    gridWidget->adjustSize();
}

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

#include "gridview.h"

#include "../global.h"
#include "../common.h"

#include "widgets/clickablewidget.h"

#include <QFile>
#include <QFileInfo>
#include <QGridLayout>
#include <QKeyEvent>
#include <QLabel>
#include <QScrollArea>
#include <QScrollBar>

GridView::GridView(QWidget *parent) : QScrollArea(parent)
{
    this->parent = parent;

    setObjectName("gridView");
    setStyleSheet("#gridView { border: none; }");
    setBackgroundRole(QPalette::Dark);
    setAlignment(Qt::AlignHCenter);
    setHidden(true);

    setGridBackground();

    gridWidget = new QWidget(this);
    gridWidget->setObjectName("gridWidget");
    setWidget(gridWidget);

    gridLayout = new QGridLayout(gridWidget);
    gridLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
    gridLayout->setRowMinimumHeight(0, 10);

    gridWidget->setLayout(gridLayout);

    gridCurrent = false;
    currentGridRom = 0;
}

void GridView::addToGridView(Rom *currentRom, int count, bool ddEnabled)
{
    if (ddEnabled) // Add place for "No Cart" entry
        count++;

    ClickableWidget *gameGridItem = new ClickableWidget(gridWidget);
    gameGridItem->setMinimumWidth(getGridSize("width"));
    gameGridItem->setMaximumWidth(getGridSize("width"));
    gameGridItem->setGraphicsEffect(getShadow(false));
    gameGridItem->setContextMenuPolicy(Qt::CustomContextMenu);

    //Assign ROM data to widget for use in click events
    gameGridItem->setProperty("fileName", currentRom->fileName);
    gameGridItem->setProperty("directory", currentRom->directory);
    if (currentRom->goodName == getTranslation("Unknown ROM") ||
        currentRom->goodName == getTranslation("Requires catalog file"))
        gameGridItem->setProperty("search", currentRom->internalName);
    else
        gameGridItem->setProperty("search", currentRom->goodName);
    gameGridItem->setProperty("romMD5", currentRom->romMD5);
    gameGridItem->setProperty("zipFile", currentRom->zipFile);

    QGridLayout *gameGridLayout = new QGridLayout(gameGridItem);
    gameGridLayout->setColumnStretch(0, 1);
    gameGridLayout->setColumnStretch(3, 1);
    gameGridLayout->setRowMinimumHeight(1, getImageSize("Grid").height());

    QLabel *gridImageLabel = new QLabel(gameGridItem);
    gridImageLabel->setMinimumHeight(getImageSize("Grid").height());
    gridImageLabel->setMinimumWidth(getImageSize("Grid").width());
    QPixmap image;

    if (currentRom->imageExists) {
        //Use uniform aspect ratio to account for fluctuations in TheGamesDB box art
        Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio;

        //Don't warp aspect ratio though if image is too far away from standard size (JP box art)
        float aspectRatio = float(currentRom->image.width()) / currentRom->image.height();

        if (aspectRatio < 1.1 || aspectRatio > 1.8)
            aspectRatioMode = Qt::KeepAspectRatio;

        image = currentRom->image.scaled(getImageSize("Grid"), aspectRatioMode, Qt::SmoothTransformation);
    } else {
        if (ddEnabled && count == 0)
            image = QPixmap(":/images/no-cart.png").scaled(getImageSize("Grid"), Qt::IgnoreAspectRatio,
                                                             Qt::SmoothTransformation);
        else
            image = QPixmap(":/images/not-found.png").scaled(getImageSize("Grid"), Qt::IgnoreAspectRatio,
                                                             Qt::SmoothTransformation);
    }

    gridImageLabel->setPixmap(image);
    gridImageLabel->setAlignment(Qt::AlignCenter);
    gameGridLayout->addWidget(gridImageLabel, 1, 1);

    if (SETTINGS.value("Grid/label","true") == "true") {
        QLabel *gridTextLabel = new QLabel(gameGridItem);

        //Don't allow label to be wider than image
        gridTextLabel->setMaximumWidth(getImageSize("Grid").width());

        QString text = "";
        QString labelText = SETTINGS.value("Grid/labeltext","Filename").toString();

        text = getRomInfo(labelText, currentRom);

        if (ddEnabled && count == 0)
            text = tr("No Cart");

        gridTextLabel->setText(text);

        QString textHex = getColor(SETTINGS.value("Grid/labelcolor","White").toString()).name();
        int fontSize = getGridSize("font");

        gridTextLabel->setStyleSheet("QLabel { font-weight: bold; color: " + textHex + "; font-size: "
                                     + QString::number(fontSize) + "px; }");
        gridTextLabel->setWordWrap(true);
        gridTextLabel->setAlignment(Qt::AlignHCenter | Qt::AlignTop);

        gameGridLayout->addWidget(gridTextLabel, 2, 1);
    }

    gameGridItem->setLayout(gameGridLayout);

    gameGridItem->setMinimumHeight(gameGridItem->sizeHint().height());

    int columnCount;
    if (SETTINGS.value("Grid/autocolumns","true").toString() == "true")
        columnCount = viewport()->width() / (getGridSize("width") + 10);
    else
        columnCount = SETTINGS.value("Grid/columncount", "4").toInt();

    if (columnCount == 0) columnCount = 1;

    gridLayout->addWidget(gameGridItem, count / columnCount + 1, count % columnCount + 1);
    gridWidget->adjustSize();

    connect(gameGridItem, SIGNAL(singleClicked(QWidget*)), this, SLOT(highlightGridWidget(QWidget*)));
    connect(gameGridItem, SIGNAL(doubleClicked(QWidget*)), parent, SLOT(launchRomFromWidget(QWidget*)));
    connect(gameGridItem, SIGNAL(arrowPressed(QWidget*, QString)), this, SLOT(selectNextRom(QWidget*, QString)));
    connect(gameGridItem, SIGNAL(enterPressed(QWidget*)), parent, SLOT(launchRomFromWidget(QWidget*)));
    connect(gameGridItem, SIGNAL(customContextMenuRequested(const QPoint &)), parent, SLOT(showRomMenu(const QPoint &)));
}

int GridView::getCurrentRom()
{
    return currentGridRom;
}

QString GridView::getCurrentRomInfo(QString infoName)
{
    const char *property = infoName.toUtf8().constData();

    if (gridLayout->count() > currentGridRom)
        return gridLayout->itemAt(currentGridRom)->widget()->property(property).toString();
    return "";
}

QWidget *GridView::getCurrentRomWidget()
{
    return gridLayout->itemAt(currentGridRom)->widget();
}

bool GridView::hasSelectedRom()
{
    return gridCurrent;
}

void GridView::highlightGridWidget(QWidget *current)
{
    current->setFocus();

    //Set all to inactive shadow
    QLayoutItem *gridItem;
    for (int item = 0; (gridItem = gridLayout->itemAt(item)) != NULL; item++)
    {
        gridItem->widget()->setGraphicsEffect(getShadow(false));

        if (gridItem->widget() == current)
            currentGridRom = item;
    }

    //Set current to active shadow
    current->setGraphicsEffect(getShadow(true));

    gridCurrent = true;
    emit gridItemSelected(true);
}

void GridView::keyPressEvent(QKeyEvent *event)
{
    if ((event->key() == Qt::Key_Down || event->key() == Qt::Key_Right) && gridLayout->count() > 0) {
        highlightGridWidget(gridLayout->itemAt(0)->widget());
        ensureWidgetVisible(gridLayout->itemAt(0)->widget());
    } else
        QScrollArea::keyPressEvent(event);
}

void GridView::resetView()
{
    QLayoutItem *gridItem;
    while ((gridItem = gridLayout->takeAt(0)) != NULL)
    {
        delete gridItem->widget();
        delete gridItem;
    }

    gridCurrent = false;
}

void GridView::resizeEvent(QResizeEvent *event)
{
    int check = event->size().width() / (getGridSize("width") + 10);
    bool autoAdjustColumns = SETTINGS.value("Grid/autocolumns","true").toString() == "true";

    if (autoAdjustColumns && check != autoColumnCount && check != 0) {
        autoColumnCount = check;
        updateGridColumns(event->size().width());
    } else
        QScrollArea::resizeEvent(event);
}

void GridView::saveGridPosition()
{
    positionx = horizontalScrollBar()->value();
    positiony = verticalScrollBar()->value();

    if (gridCurrent)
        savedGridRom = currentGridRom;
    else
        savedGridRom = -1;
    savedGridRomFilename = getCurrentRomInfo("fileName");
}

void GridView::selectNextRom(QWidget* current, QString keypress)
{
    int columnCount;
    if (SETTINGS.value("Grid/autocolumns","true").toString() == "true")
        columnCount = autoColumnCount;
    else
        columnCount = SETTINGS.value("Grid/columncount", "4").toInt();

    int offset = 0;
    if (keypress == "UP")
        offset = columnCount * -1;
    else if (keypress == "DOWN")
        offset = columnCount;
    else if (keypress == "RIGHT")
        offset = 1;
    else if (keypress == "LEFT")
        offset = -1;

    QLayoutItem *gridItem;
    for (int item = 0; (gridItem = gridLayout->itemAt(item)) != NULL; item++)
    {
        if (gridItem->widget() == current && item + offset >= 0 && gridLayout->itemAt(item + offset) != NULL) {
            ensureWidgetVisible(gridLayout->itemAt(item + offset)->widget());
            highlightGridWidget(gridLayout->itemAt(item + offset)->widget());
        }
    }
}

void GridView::setGridBackground()
{
    QString theme = SETTINGS.value("Grid/theme", "Normal").toString();
    if (theme == "Light")
        setStyleSheet("#gridView { border: none; background: #FFF; } #gridWidget { background: transparent; }");
    else if (theme == "Dark")
        setStyleSheet("#gridView { border: none; background: #222; } #gridWidget { background: transparent; }");
    else
        setStyleSheet("#gridView { border: none; }");

    QString background = SETTINGS.value("Grid/background", "").toString();
    if (background != "") {
        QFile backgroundFile(background);

        if (backgroundFile.exists() && !QFileInfo(backgroundFile).isDir())
            setStyleSheet(QString()
                + "#gridView { "
                    + "border: none; "
                    + "background: url(" + background + "); "
                    + "background-attachment: fixed; "
                    + "background-position: top center; "
                + "} #gridWidget { background: transparent; } "
            );
    }
}

void GridView::setGridPosition()
{
    horizontalScrollBar()->setValue(positionx);
    verticalScrollBar()->setValue(positiony);

    //Restore selected ROM if it is in the same position
    if (savedGridRom != -1 && gridLayout->count() > savedGridRom) {
        QWidget *checkWidget = gridLayout->itemAt(savedGridRom)->widget();
        if (checkWidget->property("fileName").toString() == savedGridRomFilename)
            highlightGridWidget(checkWidget);
    }

    if (SETTINGS.value("Grid/autocolumns","true").toString() == "true")
        setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    else
        setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
}

void GridView::updateGridColumns(int width)
{
    int columnCount = width / (getGridSize("width") + 10);

    int gridCount = gridLayout->count();
    QList<QWidget*> gridItems;
    for (int count = 0; count < gridCount; count++)
        gridItems << gridLayout->takeAt(0)->widget();

    int count = 0;
    foreach(QWidget *gridItem, gridItems)
    {
        gridLayout->addWidget(gridItem, count / columnCount + 1, count % columnCount + 1);
        count++;
    }

    gridWidget->adjustSize();
}

<|endoftext|>