<commit_before>/*********************************************************************************
 *
 * Inviwo - Interactive Visualization Workshop
 *
 * Copyright (c) 2013-2021 Inviwo Foundation
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *********************************************************************************/

#include <inviwo/core/util/stringconversion.h>
#include <inviwo/core/util/exception.h>
#include <inviwo/core/util/safecstr.h>

#include <random>
#include <iomanip>
#include <clocale>

#if defined(__clang__) || defined(__GNUC__)
#include <cstdlib>
#include <memory>
#include <cxxabi.h>
#include <cwchar>
#elif defined(WIN32)
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif

#include <fmt/format.h>

namespace inviwo {

namespace util {

std::wstring toWstring(std::string_view str) {
#if defined(_WIN32)
    if (str.empty()) return std::wstring();
    int size_needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), NULL, 0);
    std::wstring result(size_needed, 0);
    MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], size_needed);
    return result;
#else
    auto state = std::mbstate_t();
    SafeCStr safestr{str};
    auto sptr = safestr.c_str();
    const char* loc = nullptr;
    size_t len = std::mbsrtowcs(nullptr, &sptr, 0, &state);
    if (len == static_cast<std::size_t>(-1)) {
        loc = std::setlocale(LC_CTYPE, nullptr);
        std::setlocale(LC_CTYPE, "en_US.UTF-8");
        len = std::mbsrtowcs(nullptr, &sptr, 0, &state);
        if (len == static_cast<std::size_t>(-1)) {
            if (loc) std::setlocale(LC_CTYPE, loc);
            throw Exception("Invalid unicode sequence", IVW_CONTEXT_CUSTOM("String Conversion"));
        }
    }
    std::wstring result(len, 0);
    std::mbsrtowcs(result.data(), &sptr, result.size(), &state);
    if (loc) std::setlocale(LC_CTYPE, loc);
    return result;
#endif
}

std::string fromWstring(std::wstring_view str) {
#if defined(_WIN32)
    int s_size = static_cast<int>(str.size());
    if (s_size == 0) {  // WideCharToMultiByte does not support zero length, handle separately.
        return {};
    }

    int length = WideCharToMultiByte(CP_UTF8, 0, str.data(), s_size, nullptr, 0, nullptr, nullptr);
    if (length == 0) {
        throw Exception(fmt::format("Invalid string conversion Error:{}", GetLastError()),
                        IVW_CONTEXT_CUSTOM("String Conversion"));
    }

    std::string result(length, 0);
    length = WideCharToMultiByte(CP_UTF8, 0, str.data(), s_size, result.data(), length, nullptr,
                                 nullptr);
    if (length == 0) {
        throw Exception(fmt::format("Invalid string conversion Error:{}", GetLastError()),
                        IVW_CONTEXT_CUSTOM("String Conversion"));
    }
    return result;
#else
    auto state = std::mbstate_t();
    std::wstring safestr(str);
    const wchar_t* sptr = safestr.data();
    const char* loc = nullptr;
    size_t len = std::wcsrtombs(nullptr, &sptr, 0, &state);
    if (len == static_cast<std::size_t>(-1)) {
        loc = std::setlocale(LC_CTYPE, nullptr);
        std::setlocale(LC_CTYPE, "en_US.UTF-8");
        len = std::wcsrtombs(nullptr, &sptr, 0, &state);
        if (len == static_cast<std::size_t>(-1)) {
            if (loc) std::setlocale(LC_CTYPE, loc);
            throw Exception("Invalid unicode sequence", IVW_CONTEXT_CUSTOM("String Conversion"));
        }
    }
    std::string result(len, 0);
    std::wcsrtombs(result.data(), &sptr, result.size(), &state);
    if (loc) std::setlocale(LC_CTYPE, loc);
    return result;
#endif
}

bool iCaseEndsWith(std::string_view str, std::string_view suffix) {
    return str.size() >= suffix.size() &&
        // Compare last part of path with the extension
        iCaseCmp(str.substr(str.size() - suffix.size()), suffix);
}

}  // namespace util

std::vector<std::string> util::splitString(std::string_view str, char delimiter) {
    std::vector<std::string> output;
    size_t first = 0;

    while (first < str.size()) {
        const auto second = str.find_first_of(delimiter, first);

        if (first != second) output.emplace_back(str.substr(first, second - first));

        if (second == std::string_view::npos) break;

        first = second + 1;
    }

    return output;
}

std::vector<std::string_view> util::splitStringView(std::string_view str, char delimiter) {
    std::vector<std::string_view> output;
    size_t first = 0;

    while (first < str.size()) {
        const auto second = str.find_first_of(delimiter, first);

        if (first != second) output.emplace_back(str.substr(first, second - first));

        if (second == std::string_view::npos) break;

        first = second + 1;
    }

    return output;
}

std::vector<std::string> splitStringWithMultipleDelimiters(std::string_view str,
                                                           std::vector<char> delimiters) {
    std::vector<std::string> output;
    size_t first = 0;
    auto delim = std::string_view{delimiters.data(), delimiters.size()};

    while (first < str.size()) {
        const auto second = str.find_first_of(delim, first);

        if (first != second) output.emplace_back(str.substr(first, second - first));

        if (second == std::string_view::npos) break;

        first = second + 1;
    }

    return output;
}

std::string removeFromString(std::string str, char char_to_remove) {
    str.erase(std::remove(str.begin(), str.end(), char_to_remove), str.end());
    return str;
}

void replaceInString(std::string& str, std::string_view oldStr, std::string_view newStr) {
    size_t pos = 0;

    while ((pos = str.find(oldStr, pos)) != std::string::npos) {
        str.replace(pos, oldStr.length(), newStr);
        pos += newStr.length();
    }
}

std::string htmlEncode(std::string_view data) {
    std::string buffer;
    buffer.reserve(data.size());
    for (size_t pos = 0; pos != data.size(); ++pos) {
        switch (data[pos]) {
            case '&':
                buffer.append("&");
                break;
            case '\"':
                buffer.append(""");
                break;
            case '\'':
                buffer.append("'");
                break;
            case '<':
                buffer.append("<");
                break;
            case '>':
                buffer.append(">");
                break;
            default:
                buffer.append(&data[pos], 1);
                break;
        }
    }
    return buffer;
}

std::string parseTypeIdName(std::string str) {

#if defined(__clang__) || defined(__GNUC__)
    struct handle {
        char* p;
        handle(char* ptr) : p(ptr) {}
        ~handle() { std::free(p); }
    };
    const char* cstr = str.c_str();
    int status = -4;
    handle result(abi::__cxa_demangle(cstr, nullptr, nullptr, &status));
    if (status == 0) str = result.p;
#else
    replaceInString(str, "class", "");
    replaceInString(str, "const", "");
#if defined(_WIN64) || defined(__x86_64__) || defined(__ppc64)
    replaceInString(str, "__ptr64", "");
#endif
#endif
    replaceInString(str, "inviwo::", "");
    return removeFromString(removeFromString(str, '*'), ' ');
}

std::string toUpper(std::string str) {
    std::transform(str.begin(), str.end(), str.begin(),
                   [](unsigned char c) { return static_cast<unsigned char>(std::toupper(c)); });
    return str;
}

std::string toLower(std::string str) {
    std::transform(str.begin(), str.end(), str.begin(),
                   [](unsigned char c) { return static_cast<unsigned char>(std::tolower(c)); });
    return str;
}

size_t countLines(std::string_view str) { return std::count(str.begin(), str.end(), '\n') + 1; }

std::string randomString(size_t length) {
    constexpr std::string_view possibleValues =
        "0123456789"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz";

    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(0, static_cast<int>(possibleValues.size()) - 1);

    std::string s(length, ' ');
    for (auto& c : s) c = possibleValues[dis(gen)];

    return s;
}

std::string dotSeperatedToPascalCase(std::string_view s) {
    std::stringstream ss;
    util::forEachStringPart(s, ".", [&](std::string_view elem) {
        if (elem.size() > 0) ss << static_cast<char>(std::toupper(elem[0]));
        if (elem.size() > 1) ss << elem.substr(1);
    });
    return ss.str();
}

std::string camelCaseToHeader(std::string_view s) {
    if (s.empty()) return {};
    std::stringstream ss;
    char previous = ' ';
    for (auto c : s) {
        if (std::isalpha(c) && std::tolower(previous) == previous && std::toupper(c) == c)
            ss << " ";
        ss << c;
        previous = c;
    }
    auto str{ss.str()};
    str[0] = static_cast<char>(std::toupper(str[0]));
    return str;
}

bool iCaseCmp(std::string_view l, std::string_view r) {
    return std::equal(l.cbegin(), l.cend(), r.cbegin(), r.cend(),
                      [](std::string_view::value_type l1, std::string_view::value_type r1) {
                          return std::tolower(l1) == std::tolower(r1);
                      });
}

bool iCaseLess(std::string_view l, std::string_view r) {
    return std::lexicographical_compare(
        l.cbegin(), l.cend(), r.cbegin(), r.cend(),
        [](std::string_view::value_type l1, std::string_view::value_type r1) {
            return std::tolower(l1) < std::tolower(r1);
        });
}

// trim from start
std::string ltrim(std::string s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](auto c) {
                return !std::isspace(static_cast<unsigned char>(c));
            }));
    return s;
}

// trim from end
std::string rtrim(std::string s) {
    s.erase(std::find_if(s.rbegin(), s.rend(),
                         [](auto c) { return !std::isspace(static_cast<unsigned char>(c)); })
                .base(),
            s.end());
    return s;
}

// trim from both ends
std::string trim(std::string s) { return ltrim(rtrim(s)); }

std::string removeSubString(std::string_view str, std::string_view strToRemove) {
    std::string newString(str);
    size_t pos;
    while ((pos = newString.find(strToRemove)) != std::string::npos) {
        newString.erase(pos, strToRemove.length());
    }
    return newString;
}

std::string msToString(double ms, bool includeZeros, bool spacing) {
    const std::string space = (spacing ? " " : "");
    std::stringstream ss;
    bool follows = false;

    size_t days = static_cast<size_t>(ms / (1000.0 * 3600.0 * 24.0));
    if (days > 0 || (follows && includeZeros)) {
        follows = true;
        ss << days << space << "d";
        ms -= 3600 * 1000 * 24 * days;
    }
    size_t hours = static_cast<size_t>(ms / (1000.0 * 3600.0));
    if (hours > 0 || (follows && includeZeros)) {
        if (follows) ss << " ";
        follows = true;
        ss << hours << space << "h";
        ms -= 3600 * 1000 * hours;
    }
    size_t minutes = static_cast<size_t>(ms / (1000.0 * 60.0));
    if (minutes > 0 || (follows && includeZeros)) {
        if (follows) ss << " ";
        follows = true;
        ss << minutes << space << "min";
        ms -= 60 * 1000 * minutes;
    }
    size_t seconds = static_cast<size_t>(ms / 1000.0);
    // combine seconds and milliseconds, iff there already something added to the string stream
    // _or_ there are more than one second
    if (seconds > 0 || (follows && includeZeros)) {
        if (follows) ss << " ";
        follows = true;
        ss << std::setprecision(4) << (ms / 1000.0) << space << "s";
    } else {
        // less than one second, no leading minutes/hours
        ss << std::setprecision(4) << ms << space << "ms";
    }

    return ss.str();
}

bool CaseInsensitiveCompare::operator()(std::string_view a, std::string_view b) const {
    return iCaseLess(a, b);
}

}  // namespace inviwo
<commit_msg>Jenkins: Format fixes<commit_after>/*********************************************************************************
 *
 * Inviwo - Interactive Visualization Workshop
 *
 * Copyright (c) 2013-2021 Inviwo Foundation
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *********************************************************************************/

#include <inviwo/core/util/stringconversion.h>
#include <inviwo/core/util/exception.h>
#include <inviwo/core/util/safecstr.h>

#include <random>
#include <iomanip>
#include <clocale>

#if defined(__clang__) || defined(__GNUC__)
#include <cstdlib>
#include <memory>
#include <cxxabi.h>
#include <cwchar>
#elif defined(WIN32)
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif

#include <fmt/format.h>

namespace inviwo {

namespace util {

std::wstring toWstring(std::string_view str) {
#if defined(_WIN32)
    if (str.empty()) return std::wstring();
    int size_needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), NULL, 0);
    std::wstring result(size_needed, 0);
    MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], size_needed);
    return result;
#else
    auto state = std::mbstate_t();
    SafeCStr safestr{str};
    auto sptr = safestr.c_str();
    const char* loc = nullptr;
    size_t len = std::mbsrtowcs(nullptr, &sptr, 0, &state);
    if (len == static_cast<std::size_t>(-1)) {
        loc = std::setlocale(LC_CTYPE, nullptr);
        std::setlocale(LC_CTYPE, "en_US.UTF-8");
        len = std::mbsrtowcs(nullptr, &sptr, 0, &state);
        if (len == static_cast<std::size_t>(-1)) {
            if (loc) std::setlocale(LC_CTYPE, loc);
            throw Exception("Invalid unicode sequence", IVW_CONTEXT_CUSTOM("String Conversion"));
        }
    }
    std::wstring result(len, 0);
    std::mbsrtowcs(result.data(), &sptr, result.size(), &state);
    if (loc) std::setlocale(LC_CTYPE, loc);
    return result;
#endif
}

std::string fromWstring(std::wstring_view str) {
#if defined(_WIN32)
    int s_size = static_cast<int>(str.size());
    if (s_size == 0) {  // WideCharToMultiByte does not support zero length, handle separately.
        return {};
    }

    int length = WideCharToMultiByte(CP_UTF8, 0, str.data(), s_size, nullptr, 0, nullptr, nullptr);
    if (length == 0) {
        throw Exception(fmt::format("Invalid string conversion Error:{}", GetLastError()),
                        IVW_CONTEXT_CUSTOM("String Conversion"));
    }

    std::string result(length, 0);
    length = WideCharToMultiByte(CP_UTF8, 0, str.data(), s_size, result.data(), length, nullptr,
                                 nullptr);
    if (length == 0) {
        throw Exception(fmt::format("Invalid string conversion Error:{}", GetLastError()),
                        IVW_CONTEXT_CUSTOM("String Conversion"));
    }
    return result;
#else
    auto state = std::mbstate_t();
    std::wstring safestr(str);
    const wchar_t* sptr = safestr.data();
    const char* loc = nullptr;
    size_t len = std::wcsrtombs(nullptr, &sptr, 0, &state);
    if (len == static_cast<std::size_t>(-1)) {
        loc = std::setlocale(LC_CTYPE, nullptr);
        std::setlocale(LC_CTYPE, "en_US.UTF-8");
        len = std::wcsrtombs(nullptr, &sptr, 0, &state);
        if (len == static_cast<std::size_t>(-1)) {
            if (loc) std::setlocale(LC_CTYPE, loc);
            throw Exception("Invalid unicode sequence", IVW_CONTEXT_CUSTOM("String Conversion"));
        }
    }
    std::string result(len, 0);
    std::wcsrtombs(result.data(), &sptr, result.size(), &state);
    if (loc) std::setlocale(LC_CTYPE, loc);
    return result;
#endif
}

bool iCaseEndsWith(std::string_view str, std::string_view suffix) {
    return str.size() >= suffix.size() &&
           // Compare last part of path with the extension
           iCaseCmp(str.substr(str.size() - suffix.size()), suffix);
}

}  // namespace util

std::vector<std::string> util::splitString(std::string_view str, char delimiter) {
    std::vector<std::string> output;
    size_t first = 0;

    while (first < str.size()) {
        const auto second = str.find_first_of(delimiter, first);

        if (first != second) output.emplace_back(str.substr(first, second - first));

        if (second == std::string_view::npos) break;

        first = second + 1;
    }

    return output;
}

std::vector<std::string_view> util::splitStringView(std::string_view str, char delimiter) {
    std::vector<std::string_view> output;
    size_t first = 0;

    while (first < str.size()) {
        const auto second = str.find_first_of(delimiter, first);

        if (first != second) output.emplace_back(str.substr(first, second - first));

        if (second == std::string_view::npos) break;

        first = second + 1;
    }

    return output;
}

std::vector<std::string> splitStringWithMultipleDelimiters(std::string_view str,
                                                           std::vector<char> delimiters) {
    std::vector<std::string> output;
    size_t first = 0;
    auto delim = std::string_view{delimiters.data(), delimiters.size()};

    while (first < str.size()) {
        const auto second = str.find_first_of(delim, first);

        if (first != second) output.emplace_back(str.substr(first, second - first));

        if (second == std::string_view::npos) break;

        first = second + 1;
    }

    return output;
}

std::string removeFromString(std::string str, char char_to_remove) {
    str.erase(std::remove(str.begin(), str.end(), char_to_remove), str.end());
    return str;
}

void replaceInString(std::string& str, std::string_view oldStr, std::string_view newStr) {
    size_t pos = 0;

    while ((pos = str.find(oldStr, pos)) != std::string::npos) {
        str.replace(pos, oldStr.length(), newStr);
        pos += newStr.length();
    }
}

std::string htmlEncode(std::string_view data) {
    std::string buffer;
    buffer.reserve(data.size());
    for (size_t pos = 0; pos != data.size(); ++pos) {
        switch (data[pos]) {
            case '&':
                buffer.append("&");
                break;
            case '\"':
                buffer.append(""");
                break;
            case '\'':
                buffer.append("'");
                break;
            case '<':
                buffer.append("<");
                break;
            case '>':
                buffer.append(">");
                break;
            default:
                buffer.append(&data[pos], 1);
                break;
        }
    }
    return buffer;
}

std::string parseTypeIdName(std::string str) {

#if defined(__clang__) || defined(__GNUC__)
    struct handle {
        char* p;
        handle(char* ptr) : p(ptr) {}
        ~handle() { std::free(p); }
    };
    const char* cstr = str.c_str();
    int status = -4;
    handle result(abi::__cxa_demangle(cstr, nullptr, nullptr, &status));
    if (status == 0) str = result.p;
#else
    replaceInString(str, "class", "");
    replaceInString(str, "const", "");
#if defined(_WIN64) || defined(__x86_64__) || defined(__ppc64)
    replaceInString(str, "__ptr64", "");
#endif
#endif
    replaceInString(str, "inviwo::", "");
    return removeFromString(removeFromString(str, '*'), ' ');
}

std::string toUpper(std::string str) {
    std::transform(str.begin(), str.end(), str.begin(),
                   [](unsigned char c) { return static_cast<unsigned char>(std::toupper(c)); });
    return str;
}

std::string toLower(std::string str) {
    std::transform(str.begin(), str.end(), str.begin(),
                   [](unsigned char c) { return static_cast<unsigned char>(std::tolower(c)); });
    return str;
}

size_t countLines(std::string_view str) { return std::count(str.begin(), str.end(), '\n') + 1; }

std::string randomString(size_t length) {
    constexpr std::string_view possibleValues =
        "0123456789"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz";

    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(0, static_cast<int>(possibleValues.size()) - 1);

    std::string s(length, ' ');
    for (auto& c : s) c = possibleValues[dis(gen)];

    return s;
}

std::string dotSeperatedToPascalCase(std::string_view s) {
    std::stringstream ss;
    util::forEachStringPart(s, ".", [&](std::string_view elem) {
        if (elem.size() > 0) ss << static_cast<char>(std::toupper(elem[0]));
        if (elem.size() > 1) ss << elem.substr(1);
    });
    return ss.str();
}

std::string camelCaseToHeader(std::string_view s) {
    if (s.empty()) return {};
    std::stringstream ss;
    char previous = ' ';
    for (auto c : s) {
        if (std::isalpha(c) && std::tolower(previous) == previous && std::toupper(c) == c)
            ss << " ";
        ss << c;
        previous = c;
    }
    auto str{ss.str()};
    str[0] = static_cast<char>(std::toupper(str[0]));
    return str;
}

bool iCaseCmp(std::string_view l, std::string_view r) {
    return std::equal(l.cbegin(), l.cend(), r.cbegin(), r.cend(),
                      [](std::string_view::value_type l1, std::string_view::value_type r1) {
                          return std::tolower(l1) == std::tolower(r1);
                      });
}

bool iCaseLess(std::string_view l, std::string_view r) {
    return std::lexicographical_compare(
        l.cbegin(), l.cend(), r.cbegin(), r.cend(),
        [](std::string_view::value_type l1, std::string_view::value_type r1) {
            return std::tolower(l1) < std::tolower(r1);
        });
}

// trim from start
std::string ltrim(std::string s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](auto c) {
                return !std::isspace(static_cast<unsigned char>(c));
            }));
    return s;
}

// trim from end
std::string rtrim(std::string s) {
    s.erase(std::find_if(s.rbegin(), s.rend(),
                         [](auto c) { return !std::isspace(static_cast<unsigned char>(c)); })
                .base(),
            s.end());
    return s;
}

// trim from both ends
std::string trim(std::string s) { return ltrim(rtrim(s)); }

std::string removeSubString(std::string_view str, std::string_view strToRemove) {
    std::string newString(str);
    size_t pos;
    while ((pos = newString.find(strToRemove)) != std::string::npos) {
        newString.erase(pos, strToRemove.length());
    }
    return newString;
}

std::string msToString(double ms, bool includeZeros, bool spacing) {
    const std::string space = (spacing ? " " : "");
    std::stringstream ss;
    bool follows = false;

    size_t days = static_cast<size_t>(ms / (1000.0 * 3600.0 * 24.0));
    if (days > 0 || (follows && includeZeros)) {
        follows = true;
        ss << days << space << "d";
        ms -= 3600 * 1000 * 24 * days;
    }
    size_t hours = static_cast<size_t>(ms / (1000.0 * 3600.0));
    if (hours > 0 || (follows && includeZeros)) {
        if (follows) ss << " ";
        follows = true;
        ss << hours << space << "h";
        ms -= 3600 * 1000 * hours;
    }
    size_t minutes = static_cast<size_t>(ms / (1000.0 * 60.0));
    if (minutes > 0 || (follows && includeZeros)) {
        if (follows) ss << " ";
        follows = true;
        ss << minutes << space << "min";
        ms -= 60 * 1000 * minutes;
    }
    size_t seconds = static_cast<size_t>(ms / 1000.0);
    // combine seconds and milliseconds, iff there already something added to the string stream
    // _or_ there are more than one second
    if (seconds > 0 || (follows && includeZeros)) {
        if (follows) ss << " ";
        follows = true;
        ss << std::setprecision(4) << (ms / 1000.0) << space << "s";
    } else {
        // less than one second, no leading minutes/hours
        ss << std::setprecision(4) << ms << space << "ms";
    }

    return ss.str();
}

bool CaseInsensitiveCompare::operator()(std::string_view a, std::string_view b) const {
    return iCaseLess(a, b);
}

}  // namespace inviwo
<|endoftext|>


<commit_before>//
// PROJECT CHRONO - http://projectchrono.org
//
// Copyright (c) 2013 Project Chrono
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file at the top level of the distribution
// and at http://projectchrono.org/license-chrono.txt.
//

//
//   Demo code about
//
//     - contacts in FEA

#include "chrono/physics/ChSystem.h"
#include "chrono/physics/ChSystemDEM.h"
#include "chrono/physics/ChBodyEasy.h"
#include "chrono/physics/ChLoadContainer.h"
#include "chrono/lcp/ChLcpIterativeMINRES.h"
#include "chrono/geometry/ChCTriangleMeshConnected.h"

#include "chrono_fea/ChElementTetra_4.h"
#include "chrono_fea/ChMesh.h"
#include "chrono_fea/ChMeshFileLoader.h"
#include "chrono_fea/ChContactSurfaceMesh.h"
#include "chrono_fea/ChContactSurfaceNodeCloud.h"
#include "chrono_fea/ChVisualizationFEAmesh.h"
#include "chrono_fea/ChElementBeamANCF.h"
#include "chrono_fea/ChBuilderBeam.h"

#include "chrono_irrlicht/ChIrrApp.h"

using namespace chrono;
using namespace chrono::geometry;
using namespace chrono::fea;
using namespace chrono::irrlicht;

using namespace irr;

int main(int argc, char* argv[]) {

    // Create a Chrono::Engine physical system
    ChSystemDEM my_system;

    // Create the Irrlicht visualization (open the Irrlicht device,
    // bind a simple user interface, etc. etc.)
    ChIrrApp application(&my_system, L"FEA contacts", core::dimension2d<u32>(800, 600), false, true);

    // Easy shortcuts to add camera, lights, logo and sky in Irrlicht scene:
    application.AddTypicalLogo();
    application.AddTypicalSky();
    application.AddTypicalLights();
    application.AddTypicalCamera(core::vector3df(0, (f32)0.6, -1));
    application.AddLightWithShadow(core::vector3df(1.5, 5.5, -2.5), core::vector3df(0, 0, 0), 3, 2.2, 7.2, 40, 512,
                                   video::SColorf(1, 1, 1));

    application.SetContactsDrawMode(ChIrrTools::CONTACT_DISTANCES);

    //
    // CREATE THE PHYSICAL SYSTEM
    //

    //collision::ChCollisionModel::SetDefaultSuggestedEnvelope(0.0); // not needed, already 0 when using ChSystemDEM
    collision::ChCollisionModel::SetDefaultSuggestedMargin(0.006); // max inside penetration - if not enough stiffness in material: troubles

    // Use this value for an outward additional layer around meshes, that can improve
    // robustness of mesh-mesh collision detection (at the cost of having unnatural inflate effect)
    double sphere_swept_thickness = 0.002;

    // Create the surface material, containing information
    // about friction etc.
    // It is a DEM-p (penalty) material that we will assign to
    // all surfaces that might generate contacts.

    auto mysurfmaterial = std::make_shared<ChMaterialSurfaceDEM>();
    mysurfmaterial->SetYoungModulus(6e4);
    mysurfmaterial->SetFriction(0.3f);
    mysurfmaterial->SetRestitution(0.2f);
    mysurfmaterial->SetAdhesion(0);

    // Create a floor:

    bool do_mesh_collision_floor = false;

    ChTriangleMeshConnected mmeshbox;
    mmeshbox.LoadWavefrontMesh(GetChronoDataFile("cube.obj"),true,true);

    if(do_mesh_collision_floor) {

        // floor as a triangle mesh surface:
        auto mfloor = std::make_shared<ChBody>();
        mfloor->SetPos(ChVector<>(0, -1, 0));
        mfloor->SetBodyFixed(true);
        mfloor->SetMaterialSurface(mysurfmaterial);
        my_system.Add(mfloor);

        mfloor->GetCollisionModel()->ClearModel();
        mfloor->GetCollisionModel()->AddTriangleMesh(mmeshbox,false, false, VNULL, ChMatrix33<>(1), sphere_swept_thickness);
        mfloor->GetCollisionModel()->BuildModel();
        mfloor->SetCollide(true);

        auto masset_meshbox = std::make_shared<ChTriangleMeshShape>();
        masset_meshbox->SetMesh(mmeshbox);
        mfloor->AddAsset(masset_meshbox);

        auto masset_texture = std::make_shared<ChTexture>();
        masset_texture->SetTextureFilename(GetChronoDataFile("concrete.jpg"));
        mfloor->AddAsset(masset_texture);

    }
    else {
        // floor as a simple collision primitive:

        auto mfloor = std::make_shared<ChBodyEasyBox>(2, 0.1, 2, 2700, true);
        mfloor->SetBodyFixed(true);
        mfloor->SetMaterialSurface(mysurfmaterial);
        my_system.Add(mfloor);

        auto masset_texture = std::make_shared<ChTexture>();
        masset_texture->SetTextureFilename(GetChronoDataFile("concrete.jpg"));
        mfloor->AddAsset(masset_texture);
    }

    // two falling objects:

    auto mcube = std::make_shared<ChBodyEasyBox>(0.1, 0.1, 0.1, 2700, true);
    mcube->SetPos(ChVector<>(0.6,0.5,0.6));
    mcube->SetMaterialSurface(mysurfmaterial);
    my_system.Add(mcube);

    auto msphere = std::make_shared<ChBodyEasySphere>(0.1, 2700, true);
    msphere->SetPos(ChVector<>(0.8,0.5,0.6));
    msphere->SetMaterialSurface(mysurfmaterial);
    my_system.Add(msphere);

    //
    // Example 1: tetrahedrons, with collisions
    //

    // Create a mesh. We will use it for tetahedrons.

    auto my_mesh = std::make_shared<ChMesh>();

    // 1) a FEA tetahedron(s):

    // Create a material, that must be assigned to each solid element in the mesh,
    // and set its parameters
    auto mmaterial = std::make_shared<ChContinuumElastic>();
    mmaterial->Set_E(0.01e9);  // rubber 0.01e9, steel 200e9
    mmaterial->Set_v(0.3);
    mmaterial->Set_RayleighDampingK(0.003);
    mmaterial->Set_density(1000);

    if (false) {
        for (int k=0; k<3; ++k)
        for (int j=0; j<3; ++j)
        for (int i=0; i<3; ++i) {
            // Creates the nodes for the tetahedron
            ChVector<> offset(j*0.21, i*0.21, k*0.21);
            auto mnode1 = std::make_shared<ChNodeFEAxyz>(ChVector<>(0, 0.1, 0) + offset);
            auto mnode2 = std::make_shared<ChNodeFEAxyz>(ChVector<>(0, 0.1, 0.2) + offset);
            auto mnode3 = std::make_shared<ChNodeFEAxyz>(ChVector<>(0, 0.3, 0) + offset);
            auto mnode4 = std::make_shared<ChNodeFEAxyz>(ChVector<>(0.2, 0.1, 0) + offset);

            my_mesh->AddNode(mnode1);
            my_mesh->AddNode(mnode2);
            my_mesh->AddNode(mnode3);
            my_mesh->AddNode(mnode4);

            auto melement1 = std::make_shared<ChElementTetra_4>();
            melement1->SetNodes(mnode1,
                                mnode2,
                                mnode3,
                                mnode4);
            melement1->SetMaterial(mmaterial);

            my_mesh->AddElement(melement1);
        }
    }

    if (true) {
        for (int i= 0; i<4; ++i) {
            try
            {
            ChCoordsys<> cdown(ChVector<>(0,-0.4,0));
            ChCoordsys<> crot(VNULL, Q_from_AngAxis(CH_C_2PI * ChRandom(), VECT_Y) * Q_from_AngAxis(CH_C_PI_2, VECT_X));
            ChCoordsys<> cydisp(ChVector<>(-0.3 ,0.1+i*0.1, -0.3));
            ChCoordsys<> ctot = cdown >> crot >> cydisp;
            ChMatrix33<> mrot(ctot.rot);
            ChMeshFileLoader::FromTetGenFile(my_mesh, GetChronoDataFile("fea/beam.node").c_str(),
                                             GetChronoDataFile("fea/beam.ele").c_str(), mmaterial, ctot.pos, mrot);
            }
            catch (ChException myerr) {
                    GetLog() << myerr.what();
                    return 0;
            }
        }
    }

    // Create the contact surface(s).
    // In this case it is a ChContactSurfaceMesh, that allows mesh-mesh collsions.

    auto mcontactsurf = std::make_shared<ChContactSurfaceMesh>();
    my_mesh->AddContactSurface(mcontactsurf);

    mcontactsurf->AddFacesFromBoundary(sphere_swept_thickness); // do this after my_mesh->AddContactSurface

    mcontactsurf->SetMaterialSurface(mysurfmaterial); // use the DEM penalty contacts

    // Remember to add the mesh to the system!
    my_system.Add(my_mesh);

    //
    // Example 2: beams, with collisions
    //

    // Create a mesh. We will use it for beams only.

    auto my_mesh_beams = std::make_shared<ChMesh>();

    // 2) an ANCF cable:

    auto msection_cable2 = std::make_shared<ChBeamSectionCable>();
	msection_cable2->SetDiameter(0.05);
	msection_cable2->SetYoungModulus (0.01e9);
	msection_cable2->SetBeamRaleyghDamping(0.05);

	ChBuilderBeamANCF builder;

	builder.BuildBeam(	my_mesh_beams,	// the mesh where to put the created nodes and elements
						msection_cable2,// the ChBeamSectionCable to use for the ChElementBeamANCF elements
						10,				// the number of ChElementBeamANCF to create
						ChVector<>(0, 0.1, -0.1),		// the 'A' point in space (beginning of beam)
						ChVector<>(0.5, 0.13, -0.1));	// the 'B' point in space (end of beam)

    // Create the contact surface(s).
    // In this case it is a ChContactSurfaceNodeCloud, so just pass
    // all nodes to it.

    auto mcontactcloud = std::make_shared<ChContactSurfaceNodeCloud>();
    my_mesh_beams->AddContactSurface(mcontactcloud);

    mcontactcloud->AddAllNodes(0.025); // use larger point size to match beam section radius

    mcontactcloud->SetMaterialSurface(mysurfmaterial);

    my_mesh->AddContactSurface(mcontactsurf);

    // Remember to add the mesh to the system!
    my_system.Add(my_mesh_beams);

    //
    // Optional...  visualization
    //

    // ==Asset== attach a visualization of the FEM mesh.
    // This will automatically update a triangle mesh (a ChTriangleMeshShape
    // asset that is internally managed) by setting  proper
    // coordinates and vertex colours as in the FEM elements.
    // Such triangle mesh can be rendered by Irrlicht or POVray or whatever
    // postprocessor that can handle a coloured ChTriangleMeshShape).
    // Do not forget AddAsset() at the end!

    auto mvisualizemesh = std::make_shared<ChVisualizationFEAmesh>(*(my_mesh.get()));
    mvisualizemesh->SetFEMdataType(ChVisualizationFEAmesh::E_PLOT_NODE_SPEED_NORM);
    mvisualizemesh->SetColorscaleMinMax(0.0, 5.50);
    mvisualizemesh->SetSmoothFaces(true);
    my_mesh->AddAsset(mvisualizemesh);

    auto mvisualizemeshcoll = std::make_shared<ChVisualizationFEAmesh>(*(my_mesh.get()));
    mvisualizemeshcoll->SetFEMdataType(ChVisualizationFEAmesh::E_PLOT_CONTACTSURFACES);
    mvisualizemeshcoll->SetWireframe(true);
    mvisualizemeshcoll->SetDefaultMeshColor(ChColor(1,0.5,0));
    my_mesh->AddAsset(mvisualizemeshcoll);

    auto mvisualizemeshbeam = std::make_shared<ChVisualizationFEAmesh>(*(my_mesh_beams.get()));
    mvisualizemeshbeam->SetFEMdataType(ChVisualizationFEAmesh::E_PLOT_NODE_SPEED_NORM);
    mvisualizemeshbeam->SetColorscaleMinMax(0.0, 5.50);
    mvisualizemeshbeam->SetSmoothFaces(true);
    my_mesh->AddAsset(mvisualizemeshbeam);

    auto mvisualizemeshbeamnodes = std::make_shared<ChVisualizationFEAmesh>(*(my_mesh_beams.get()));
    mvisualizemeshbeamnodes->SetFEMglyphType(ChVisualizationFEAmesh::E_GLYPH_NODE_DOT_POS);
    mvisualizemeshbeamnodes->SetFEMdataType(ChVisualizationFEAmesh::E_PLOT_NONE);
    mvisualizemeshbeamnodes->SetSymbolsThickness(0.008);
    my_mesh->AddAsset(mvisualizemeshbeamnodes);

    // ==IMPORTANT!== Use this function for adding a ChIrrNodeAsset to all items
    // in the system. These ChIrrNodeAsset assets are 'proxies' to the Irrlicht meshes.
    // If you need a finer control on which item really needs a visualization proxy in
    // Irrlicht, just use application.AssetBind(myitem); on a per-item basis.

    application.AssetBindAll();

    // ==IMPORTANT!== Use this function for 'converting' into Irrlicht meshes the assets
    // that you added to the bodies into 3D shapes, they can be visualized by Irrlicht!

    application.AssetUpdateAll();

    // Use shadows in realtime view
    application.AddShadowAll();

    // Mark completion of system construction
    my_system.SetupInitial();

    //
    // THE SOFT-REAL-TIME CYCLE
    //

    my_system.SetLcpSolverType(ChSystem::LCP_ITERATIVE_MINRES);
    my_system.SetIterLCPwarmStarting(true);  // this helps a lot to speedup convergence in this class of problems
    my_system.SetIterLCPmaxItersSpeed(40);
    my_system.SetTolForce(1e-10);
    my_system.SetIntegrationType(chrono::ChSystem::INT_EULER_IMPLICIT_LINEARIZED);

    //***TEST***
    /*
    ChMatlabEngine matlab_engine;
    ChLcpMatlabSolver* matlab_solver_stab  = new ChLcpMatlabSolver(matlab_engine);
    ChLcpMatlabSolver* matlab_solver_speed = new ChLcpMatlabSolver(matlab_engine);
    my_system.ChangeLcpSolverStab (matlab_solver_stab);
    my_system.ChangeLcpSolverSpeed(matlab_solver_speed);
    */
    application.SetTimestep(0.001);

    while (application.GetDevice()->run()) {
        application.BeginScene();

        application.DrawAll();

        application.DoStep();

        application.EndScene();
    }

    return 0;
}
<commit_msg>This line was typed twice. Removed.<commit_after>//
// PROJECT CHRONO - http://projectchrono.org
//
// Copyright (c) 2013 Project Chrono
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file at the top level of the distribution
// and at http://projectchrono.org/license-chrono.txt.
//

//
//   Demo code about
//
//     - contacts in FEA

#include "chrono/physics/ChSystem.h"
#include "chrono/physics/ChSystemDEM.h"
#include "chrono/physics/ChBodyEasy.h"
#include "chrono/physics/ChLoadContainer.h"
#include "chrono/lcp/ChLcpIterativeMINRES.h"
#include "chrono/geometry/ChCTriangleMeshConnected.h"

#include "chrono_fea/ChElementTetra_4.h"
#include "chrono_fea/ChMesh.h"
#include "chrono_fea/ChMeshFileLoader.h"
#include "chrono_fea/ChContactSurfaceMesh.h"
#include "chrono_fea/ChContactSurfaceNodeCloud.h"
#include "chrono_fea/ChVisualizationFEAmesh.h"
#include "chrono_fea/ChElementBeamANCF.h"
#include "chrono_fea/ChBuilderBeam.h"

#include "chrono_irrlicht/ChIrrApp.h"

using namespace chrono;
using namespace chrono::geometry;
using namespace chrono::fea;
using namespace chrono::irrlicht;

using namespace irr;

int main(int argc, char* argv[]) {

    // Create a Chrono::Engine physical system
    ChSystemDEM my_system;

    // Create the Irrlicht visualization (open the Irrlicht device,
    // bind a simple user interface, etc. etc.)
    ChIrrApp application(&my_system, L"FEA contacts", core::dimension2d<u32>(800, 600), false, true);

    // Easy shortcuts to add camera, lights, logo and sky in Irrlicht scene:
    application.AddTypicalLogo();
    application.AddTypicalSky();
    application.AddTypicalLights();
    application.AddTypicalCamera(core::vector3df(0, (f32)0.6, -1));
    application.AddLightWithShadow(core::vector3df(1.5, 5.5, -2.5), core::vector3df(0, 0, 0), 3, 2.2, 7.2, 40, 512,
                                   video::SColorf(1, 1, 1));

    application.SetContactsDrawMode(ChIrrTools::CONTACT_DISTANCES);

    //
    // CREATE THE PHYSICAL SYSTEM
    //

    //collision::ChCollisionModel::SetDefaultSuggestedEnvelope(0.0); // not needed, already 0 when using ChSystemDEM
    collision::ChCollisionModel::SetDefaultSuggestedMargin(0.006); // max inside penetration - if not enough stiffness in material: troubles

    // Use this value for an outward additional layer around meshes, that can improve
    // robustness of mesh-mesh collision detection (at the cost of having unnatural inflate effect)
    double sphere_swept_thickness = 0.002;

    // Create the surface material, containing information
    // about friction etc.
    // It is a DEM-p (penalty) material that we will assign to
    // all surfaces that might generate contacts.

    auto mysurfmaterial = std::make_shared<ChMaterialSurfaceDEM>();
    mysurfmaterial->SetYoungModulus(6e4);
    mysurfmaterial->SetFriction(0.3f);
    mysurfmaterial->SetRestitution(0.2f);
    mysurfmaterial->SetAdhesion(0);

    // Create a floor:

    bool do_mesh_collision_floor = false;

    ChTriangleMeshConnected mmeshbox;
    mmeshbox.LoadWavefrontMesh(GetChronoDataFile("cube.obj"),true,true);

    if(do_mesh_collision_floor) {

        // floor as a triangle mesh surface:
        auto mfloor = std::make_shared<ChBody>();
        mfloor->SetPos(ChVector<>(0, -1, 0));
        mfloor->SetBodyFixed(true);
        mfloor->SetMaterialSurface(mysurfmaterial);
        my_system.Add(mfloor);

        mfloor->GetCollisionModel()->ClearModel();
        mfloor->GetCollisionModel()->AddTriangleMesh(mmeshbox,false, false, VNULL, ChMatrix33<>(1), sphere_swept_thickness);
        mfloor->GetCollisionModel()->BuildModel();
        mfloor->SetCollide(true);

        auto masset_meshbox = std::make_shared<ChTriangleMeshShape>();
        masset_meshbox->SetMesh(mmeshbox);
        mfloor->AddAsset(masset_meshbox);

        auto masset_texture = std::make_shared<ChTexture>();
        masset_texture->SetTextureFilename(GetChronoDataFile("concrete.jpg"));
        mfloor->AddAsset(masset_texture);

    }
    else {
        // floor as a simple collision primitive:

        auto mfloor = std::make_shared<ChBodyEasyBox>(2, 0.1, 2, 2700, true);
        mfloor->SetBodyFixed(true);
        mfloor->SetMaterialSurface(mysurfmaterial);
        my_system.Add(mfloor);

        auto masset_texture = std::make_shared<ChTexture>();
        masset_texture->SetTextureFilename(GetChronoDataFile("concrete.jpg"));
        mfloor->AddAsset(masset_texture);
    }

    // two falling objects:

    auto mcube = std::make_shared<ChBodyEasyBox>(0.1, 0.1, 0.1, 2700, true);
    mcube->SetPos(ChVector<>(0.6,0.5,0.6));
    mcube->SetMaterialSurface(mysurfmaterial);
    my_system.Add(mcube);

    auto msphere = std::make_shared<ChBodyEasySphere>(0.1, 2700, true);
    msphere->SetPos(ChVector<>(0.8,0.5,0.6));
    msphere->SetMaterialSurface(mysurfmaterial);
    my_system.Add(msphere);

    //
    // Example 1: tetrahedrons, with collisions
    //

    // Create a mesh. We will use it for tetahedrons.

    auto my_mesh = std::make_shared<ChMesh>();

    // 1) a FEA tetahedron(s):

    // Create a material, that must be assigned to each solid element in the mesh,
    // and set its parameters
    auto mmaterial = std::make_shared<ChContinuumElastic>();
    mmaterial->Set_E(0.01e9);  // rubber 0.01e9, steel 200e9
    mmaterial->Set_v(0.3);
    mmaterial->Set_RayleighDampingK(0.003);
    mmaterial->Set_density(1000);

    if (false) {
        for (int k=0; k<3; ++k)
        for (int j=0; j<3; ++j)
        for (int i=0; i<3; ++i) {
            // Creates the nodes for the tetahedron
            ChVector<> offset(j*0.21, i*0.21, k*0.21);
            auto mnode1 = std::make_shared<ChNodeFEAxyz>(ChVector<>(0, 0.1, 0) + offset);
            auto mnode2 = std::make_shared<ChNodeFEAxyz>(ChVector<>(0, 0.1, 0.2) + offset);
            auto mnode3 = std::make_shared<ChNodeFEAxyz>(ChVector<>(0, 0.3, 0) + offset);
            auto mnode4 = std::make_shared<ChNodeFEAxyz>(ChVector<>(0.2, 0.1, 0) + offset);

            my_mesh->AddNode(mnode1);
            my_mesh->AddNode(mnode2);
            my_mesh->AddNode(mnode3);
            my_mesh->AddNode(mnode4);

            auto melement1 = std::make_shared<ChElementTetra_4>();
            melement1->SetNodes(mnode1,
                                mnode2,
                                mnode3,
                                mnode4);
            melement1->SetMaterial(mmaterial);

            my_mesh->AddElement(melement1);
        }
    }

    if (true) {
        for (int i= 0; i<4; ++i) {
            try
            {
            ChCoordsys<> cdown(ChVector<>(0,-0.4,0));
            ChCoordsys<> crot(VNULL, Q_from_AngAxis(CH_C_2PI * ChRandom(), VECT_Y) * Q_from_AngAxis(CH_C_PI_2, VECT_X));
            ChCoordsys<> cydisp(ChVector<>(-0.3 ,0.1+i*0.1, -0.3));
            ChCoordsys<> ctot = cdown >> crot >> cydisp;
            ChMatrix33<> mrot(ctot.rot);
            ChMeshFileLoader::FromTetGenFile(my_mesh, GetChronoDataFile("fea/beam.node").c_str(),
                                             GetChronoDataFile("fea/beam.ele").c_str(), mmaterial, ctot.pos, mrot);
            }
            catch (ChException myerr) {
                    GetLog() << myerr.what();
                    return 0;
            }
        }
    }

    // Create the contact surface(s).
    // In this case it is a ChContactSurfaceMesh, that allows mesh-mesh collsions.

    auto mcontactsurf = std::make_shared<ChContactSurfaceMesh>();
    my_mesh->AddContactSurface(mcontactsurf);

    mcontactsurf->AddFacesFromBoundary(sphere_swept_thickness); // do this after my_mesh->AddContactSurface

    mcontactsurf->SetMaterialSurface(mysurfmaterial); // use the DEM penalty contacts

    // Remember to add the mesh to the system!
    my_system.Add(my_mesh);

    //
    // Example 2: beams, with collisions
    //

    // Create a mesh. We will use it for beams only.

    auto my_mesh_beams = std::make_shared<ChMesh>();

    // 2) an ANCF cable:

    auto msection_cable2 = std::make_shared<ChBeamSectionCable>();
	msection_cable2->SetDiameter(0.05);
	msection_cable2->SetYoungModulus (0.01e9);
	msection_cable2->SetBeamRaleyghDamping(0.05);

	ChBuilderBeamANCF builder;

	builder.BuildBeam(	my_mesh_beams,	// the mesh where to put the created nodes and elements
						msection_cable2,// the ChBeamSectionCable to use for the ChElementBeamANCF elements
						10,				// the number of ChElementBeamANCF to create
						ChVector<>(0, 0.1, -0.1),		// the 'A' point in space (beginning of beam)
						ChVector<>(0.5, 0.13, -0.1));	// the 'B' point in space (end of beam)

    // Create the contact surface(s).
    // In this case it is a ChContactSurfaceNodeCloud, so just pass
    // all nodes to it.

    auto mcontactcloud = std::make_shared<ChContactSurfaceNodeCloud>();
    my_mesh_beams->AddContactSurface(mcontactcloud);

    mcontactcloud->AddAllNodes(0.025); // use larger point size to match beam section radius

    mcontactcloud->SetMaterialSurface(mysurfmaterial);

    // Remember to add the mesh to the system!
    my_system.Add(my_mesh_beams);

    //
    // Optional...  visualization
    //

    // ==Asset== attach a visualization of the FEM mesh.
    // This will automatically update a triangle mesh (a ChTriangleMeshShape
    // asset that is internally managed) by setting  proper
    // coordinates and vertex colours as in the FEM elements.
    // Such triangle mesh can be rendered by Irrlicht or POVray or whatever
    // postprocessor that can handle a coloured ChTriangleMeshShape).
    // Do not forget AddAsset() at the end!

    auto mvisualizemesh = std::make_shared<ChVisualizationFEAmesh>(*(my_mesh.get()));
    mvisualizemesh->SetFEMdataType(ChVisualizationFEAmesh::E_PLOT_NODE_SPEED_NORM);
    mvisualizemesh->SetColorscaleMinMax(0.0, 5.50);
    mvisualizemesh->SetSmoothFaces(true);
    my_mesh->AddAsset(mvisualizemesh);

    auto mvisualizemeshcoll = std::make_shared<ChVisualizationFEAmesh>(*(my_mesh.get()));
    mvisualizemeshcoll->SetFEMdataType(ChVisualizationFEAmesh::E_PLOT_CONTACTSURFACES);
    mvisualizemeshcoll->SetWireframe(true);
    mvisualizemeshcoll->SetDefaultMeshColor(ChColor(1,0.5,0));
    my_mesh->AddAsset(mvisualizemeshcoll);

    auto mvisualizemeshbeam = std::make_shared<ChVisualizationFEAmesh>(*(my_mesh_beams.get()));
    mvisualizemeshbeam->SetFEMdataType(ChVisualizationFEAmesh::E_PLOT_NODE_SPEED_NORM);
    mvisualizemeshbeam->SetColorscaleMinMax(0.0, 5.50);
    mvisualizemeshbeam->SetSmoothFaces(true);
    my_mesh->AddAsset(mvisualizemeshbeam);

    auto mvisualizemeshbeamnodes = std::make_shared<ChVisualizationFEAmesh>(*(my_mesh_beams.get()));
    mvisualizemeshbeamnodes->SetFEMglyphType(ChVisualizationFEAmesh::E_GLYPH_NODE_DOT_POS);
    mvisualizemeshbeamnodes->SetFEMdataType(ChVisualizationFEAmesh::E_PLOT_NONE);
    mvisualizemeshbeamnodes->SetSymbolsThickness(0.008);
    my_mesh->AddAsset(mvisualizemeshbeamnodes);

    // ==IMPORTANT!== Use this function for adding a ChIrrNodeAsset to all items
    // in the system. These ChIrrNodeAsset assets are 'proxies' to the Irrlicht meshes.
    // If you need a finer control on which item really needs a visualization proxy in
    // Irrlicht, just use application.AssetBind(myitem); on a per-item basis.

    application.AssetBindAll();

    // ==IMPORTANT!== Use this function for 'converting' into Irrlicht meshes the assets
    // that you added to the bodies into 3D shapes, they can be visualized by Irrlicht!

    application.AssetUpdateAll();

    // Use shadows in realtime view
    application.AddShadowAll();

    // Mark completion of system construction
    my_system.SetupInitial();

    //
    // THE SOFT-REAL-TIME CYCLE
    //

    my_system.SetLcpSolverType(ChSystem::LCP_ITERATIVE_MINRES);
    my_system.SetIterLCPwarmStarting(true);  // this helps a lot to speedup convergence in this class of problems
    my_system.SetIterLCPmaxItersSpeed(40);
    my_system.SetTolForce(1e-10);
    my_system.SetIntegrationType(chrono::ChSystem::INT_EULER_IMPLICIT_LINEARIZED);

    //***TEST***
    /*
    ChMatlabEngine matlab_engine;
    ChLcpMatlabSolver* matlab_solver_stab  = new ChLcpMatlabSolver(matlab_engine);
    ChLcpMatlabSolver* matlab_solver_speed = new ChLcpMatlabSolver(matlab_engine);
    my_system.ChangeLcpSolverStab (matlab_solver_stab);
    my_system.ChangeLcpSolverSpeed(matlab_solver_speed);
    */
    application.SetTimestep(0.001);

    while (application.GetDevice()->run()) {
        application.BeginScene();

        application.DrawAll();

        application.DoStep();

        application.EndScene();
    }

    return 0;
}
<|endoftext|>


<commit_before>/*
* Copyright (C) 2008-2013 J-P Nurmi <jpnurmi@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*/

#include "messageview.h"
#include "menufactory.h"
#include "completer.h"
#include "usermodel.h"
#include "session.h"
#include <QAbstractTextDocumentLayout>
#include <QDesktopServices>
#include <QStringListModel>
#include <QTextBlock>
#include <QShortcut>
#include <QKeyEvent>
#include <QDateTime>
#include <QDebug>
#include <QUrl>
#include <ircmessage.h>
#include <irccommand.h>
#include <irc.h>

static QStringListModel* command_model = 0;
static const int VERTICAL_MARGIN = 1; // matches qlineedit_p.cpp

MessageView::MessageView(MessageView::ViewType type, Session* session, QWidget* parent) :
    QWidget(parent)
{
    d.setupUi(this);
    d.viewType = type;
    d.joined = false;

    d.topicLabel->setMinimumHeight(d.lineEditor->sizeHint().height());
    d.helpLabel->setMinimumHeight(d.lineEditor->sizeHint().height());

    connect(d.splitter, SIGNAL(splitterMoved(int, int)), this, SLOT(onSplitterMoved()));

    setFocusProxy(d.lineEditor);
    d.textBrowser->setBuddy(d.lineEditor);
    d.textBrowser->viewport()->installEventFilter(this);
    connect(d.textBrowser, SIGNAL(anchorClicked(QUrl)), SLOT(onAnchorClicked(QUrl)));

    d.formatter = new MessageFormatter(this);

    d.session = session;
    connect(d.session, SIGNAL(activeChanged(bool)), this, SIGNAL(activeChanged()));
    connect(&d.parser, SIGNAL(customCommand(QString, QStringList)), this, SLOT(onCustomCommand(QString, QStringList)));

    d.topicLabel->setVisible(type == ChannelView);
    d.listView->setVisible(type == ChannelView);
    if (type == ChannelView) {
        d.listView->setSession(session);
        connect(d.listView, SIGNAL(queried(QString)), this, SIGNAL(queried(QString)));
        connect(d.listView, SIGNAL(doubleClicked(QString)), this, SIGNAL(queried(QString)));
        connect(d.listView, SIGNAL(commandRequested(IrcCommand*)), d.session, SLOT(sendCommand(IrcCommand*)));
    }

    if (!command_model) {
        CommandParser::addCustomCommand("CLEAR", "");
        CommandParser::addCustomCommand("QUERY", "<user>");
        CommandParser::addCustomCommand("MSG", "<usr/channel> <message...>");

        QStringList prefixedCommands;
        foreach (const QString& command, CommandParser::availableCommands())
            prefixedCommands += "/" + command;

        command_model = new QStringListModel(qApp);
        command_model->setStringList(prefixedCommands);
    }

    d.lineEditor->completer()->setDefaultModel(d.listView->userModel());
    d.lineEditor->completer()->setSlashModel(command_model);

    connect(d.lineEditor, SIGNAL(send(QString)), this, SLOT(sendMessage(QString)));
    connect(d.lineEditor, SIGNAL(typed(QString)), this, SLOT(showHelp(QString)));

    d.helpLabel->hide();
    d.searchEditor->setTextEdit(d.textBrowser);

    QShortcut* shortcut = new QShortcut(Qt::Key_Escape, this);
    connect(shortcut, SIGNAL(activated()), this, SLOT(onEscPressed()));
}

MessageView::~MessageView()
{
}

bool MessageView::isActive() const
{
    if (!d.session->isActive())
        return false;
    if (d.viewType == ChannelView)
        return d.joined;
    return true;
}

MessageView::ViewType MessageView::viewType() const
{
    return d.viewType;
}

Session* MessageView::session() const
{
    return d.session;
}

UserModel* MessageView::userModel() const
{
    return d.listView->userModel();
}

QTextBrowser* MessageView::textBrowser() const
{
    return d.textBrowser;
}

MessageFormatter* MessageView::messageFormatter() const
{
    return d.formatter;
}

QString MessageView::receiver() const
{
    return d.receiver;
}

void MessageView::setReceiver(const QString& receiver)
{
    if (d.receiver != receiver) {
        d.receiver = receiver;
        if (d.viewType == ChannelView)
            d.listView->setChannel(receiver);
        emit receiverChanged(receiver);
    }
}

MenuFactory* MessageView::menuFactory() const
{
    return d.listView->menuFactory();
}

void MessageView::setMenuFactory(MenuFactory* factory)
{
    d.listView->setMenuFactory(factory);
}

QByteArray MessageView::saveSplitter() const
{
    if (d.viewType != ServerView)
        return d.splitter->saveState();
    return QByteArray();
}

void MessageView::restoreSplitter(const QByteArray& state)
{
    d.splitter->restoreState(state);
}

void MessageView::showHelp(const QString& text, bool error)
{
    QString syntax;
    if (text == "/") {
        QStringList commands = CommandParser::availableCommands();
        syntax = commands.join(" ");
    } else if (text.startsWith('/')) {
        QStringList words = text.mid(1).split(' ');
        QString command = words.value(0);
        QStringList suggestions = CommandParser::suggestedCommands(command, words.mid(1));
        if (suggestions.count() == 1)
            syntax = CommandParser::syntax(suggestions.first());
        else
            syntax = suggestions.join(" ");

        if (syntax.isEmpty() && error)
            syntax = tr("Unknown command '%1'").arg(command.toUpper());
    }

    d.helpLabel->setVisible(!syntax.isEmpty());
    QPalette pal;
    if (error)
        pal.setColor(QPalette::WindowText, d.settings.colors[Settings::Highlight]);
    d.helpLabel->setPalette(pal);
    d.helpLabel->setText(syntax);
}

void MessageView::sendMessage(const QString& message)
{
    QStringList lines = message.split(QRegExp("[\\r\\n]"), QString::SkipEmptyParts);
    foreach (const QString& line, lines) {
        IrcCommand* cmd = d.parser.parseCommand(receiver(), line);
        if (cmd) {
            d.session->sendCommand(cmd);

            if (cmd->type() == IrcCommand::Message || cmd->type() == IrcCommand::CtcpAction || cmd->type() == IrcCommand::Notice) {
                IrcMessage* msg = IrcMessage::fromData(":" + d.session->nickName().toUtf8() + " " + cmd->toString().toUtf8(), d.session);
                receiveMessage(msg);
                delete msg;
            }
        } else if (d.parser.hasError()) {
            showHelp(line, true);
        }
    }
}

void MessageView::appendMessage(const QString& message)
{
    if (!message.isEmpty()) {
        // workaround the link activation merge char format bug
        QString copy = message;
        if (copy.endsWith("</a>"))
            copy += " ";

        d.textBrowser->append(copy);
        if (!isVisible() && d.textBrowser->unseenBlock() == -1)
            d.textBrowser->setUnseenBlock(d.textBrowser->document()->blockCount() - 1);

#if QT_VERSION >= 0x040800
        QTextBlock block = d.textBrowser->document()->lastBlock();
        QTextBlockFormat format = block.blockFormat();
        format.setLineHeight(120, QTextBlockFormat::ProportionalHeight);
        QTextCursor cursor(block);
        cursor.setBlockFormat(format);
#endif // QT_VERSION
    }
}

void MessageView::hideEvent(QHideEvent* event)
{
    QWidget::hideEvent(event);
    d.textBrowser->setUnseenBlock(-1);
}

bool MessageView::eventFilter(QObject* object, QEvent* event)
{
    if (object == d.textBrowser->viewport() && event->type() == QEvent::ContextMenu) {
        QContextMenuEvent* menuEvent = static_cast<QContextMenuEvent*>(event);
        QAbstractTextDocumentLayout* layout = d.textBrowser->document()->documentLayout();
        QUrl link(layout->anchorAt(menuEvent->pos()));
        if (link.scheme() == "nick") {
            QMenu* standardMenu = d.textBrowser->createStandardContextMenu(menuEvent->pos());
            QMenu* customMenu = d.listView->menuFactory()->createUserViewMenu(link.toString(QUrl::RemoveScheme), this);
            customMenu->addSeparator();
            customMenu->insertActions(0, standardMenu->actions());
            customMenu->exec(menuEvent->globalPos());
            customMenu->deleteLater();
            delete standardMenu;
            return true;
        }
    }
    return QWidget::eventFilter(object, event);
}

void MessageView::onEscPressed()
{
    d.helpLabel->hide();
    d.searchEditor->hide();
    setFocus(Qt::OtherFocusReason);
}

void MessageView::onSplitterMoved()
{
    emit splitterChanged(d.splitter->saveState());
}

void MessageView::onAnchorClicked(const QUrl& link)
{
    if (link.scheme() == "nick")
        emit queried(link.toString(QUrl::RemoveScheme));
    else
        QDesktopServices::openUrl(link);
}

void MessageView::applySettings(const Settings& settings)
{
    d.formatter->setTimeStamp(settings.timeStamp);
    d.formatter->setTimeStampFormat(settings.timeStampFormat);
    d.formatter->setStripNicks(settings.stripNicks);

    if (!settings.font.isEmpty())
        d.textBrowser->setFont(settings.font);
    d.textBrowser->document()->setMaximumBlockCount(settings.maxBlockCount);

    QTextDocument* doc = d.topicLabel->findChild<QTextDocument*>();
    if (doc)
        doc->setDefaultStyleSheet(QString("a { color: %1 }").arg(settings.colors.value(Settings::Link)));

    QString backgroundColor = settings.colors.value(Settings::Background);
    d.textBrowser->setStyleSheet(QString("QTextBrowser { background-color: %1 }").arg(backgroundColor));

    d.textBrowser->document()->setDefaultStyleSheet(
        QString(
            ".highlight { color: %1 }"
            ".message   { color: %2 }"
            ".notice    { color: %3 }"
            ".action    { color: %4 }"
            ".event     { color: %5 }"
            ".timestamp { color: %6; font-size: small }"
            "a { color: %7 }"
        ).arg(settings.colors.value(Settings::Highlight))
        .arg(settings.colors.value(Settings::Message))
        .arg(settings.colors.value(Settings::Notice))
        .arg(settings.colors.value(Settings::Action))
        .arg(settings.colors.value(Settings::Event))
        .arg(settings.colors.value(Settings::TimeStamp))
        .arg(settings.colors.value(Settings::Link)));
    d.settings = settings;
}

void MessageView::receiveMessage(IrcMessage* message)
{
    if (d.viewType == ChannelView)
        d.listView->processMessage(message);

    switch (message->type()) {
        case IrcMessage::Private: {
            IrcSender sender = message->sender();
            if (sender.name() == QLatin1String("***") && sender.user() == QLatin1String("znc")) {
                QString content = static_cast<IrcPrivateMessage*>(message)->message();
                if (content == QLatin1String("Buffer Playback..."))
                    d.formatter->setZncPlaybackMode(true);
                else if (content == QLatin1String("Playback Complete."))
                    d.formatter->setZncPlaybackMode(false);
            }
            break;
        }
        case IrcMessage::Topic:
            d.topicLabel->setText(d.formatter->formatHtml(static_cast<IrcTopicMessage*>(message)->topic()));
            if (d.topicLabel->text().isEmpty())
                d.topicLabel->setText(tr("-"));
            break;
        case IrcMessage::Unknown:
            qWarning() << "unknown:" << message;
            break;
        case IrcMessage::Join:
            if (message->flags() & IrcMessage::Own) {
                d.joined = true;
                emit activeChanged();
            }
            break;
        case IrcMessage::Part:
            if (message->flags() & IrcMessage::Own) {
                d.joined = false;
                emit activeChanged();
            }
            break;
        case IrcMessage::Numeric:
            switch (static_cast<IrcNumericMessage*>(message)->code()) {
                case Irc::RPL_NOTOPIC:
                    d.topicLabel->setText(tr("-"));
                    break;
                case Irc::RPL_TOPIC:
                    d.topicLabel->setText(d.formatter->formatHtml(message->parameters().value(2)));
                    break;
                case Irc::RPL_TOPICWHOTIME: {
                    QDateTime dateTime = QDateTime::fromTime_t(message->parameters().value(3).toInt());
                    d.topicLabel->setToolTip(tr("Set %1 by %2").arg(dateTime.toString(), message->parameters().value(2)));
                    break;
                }
                default:
                    break;
            }
            break;
        default:
            break;
    }

    d.formatter->setHighlights(QStringList() << d.session->nickName());
    QString formatted = d.formatter->formatMessage(message, d.listView->userModel());
    if (formatted.length()) {
        if (!isVisible() || !isActiveWindow()) {
            IrcMessage::Type type = d.formatter->effectiveMessageType();
            if (d.formatter->hasHighlight() || (type == IrcMessage::Private && d.viewType != ChannelView))
                emit highlighted(message);
            else if (type == IrcMessage::Notice || type == IrcMessage::Private) // TODO: || (!d.receivedCodes.contains(Irc::RPL_ENDOFMOTD) && d.viewType == ServerView))
                emit missed(message);
        }

        appendMessage(formatted);
    }
}

bool MessageView::hasUser(const QString& user) const
{
    return (!d.session->nickName().compare(user, Qt::CaseInsensitive)) ||
           (d.viewType == QueryView && !d.receiver.compare(user, Qt::CaseInsensitive)) ||
           (d.viewType == ChannelView && d.listView->hasUser(user));
}

void MessageView::onCustomCommand(const QString& command, const QStringList& params)
{
    if (command == "CLEAR")
        d.textBrowser->clear();
    else if (command == "MSG")
        emit messaged(params.value(0), QStringList(params.mid(1)).join(" "));
    else if (command == "QUERY")
        emit queried(params.value(0));
}
<commit_msg>Ignore ZNC playback begin/end messages in badge numbers<commit_after>/*
* Copyright (C) 2008-2013 J-P Nurmi <jpnurmi@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*/

#include "messageview.h"
#include "menufactory.h"
#include "completer.h"
#include "usermodel.h"
#include "session.h"
#include <QAbstractTextDocumentLayout>
#include <QDesktopServices>
#include <QStringListModel>
#include <QTextBlock>
#include <QShortcut>
#include <QKeyEvent>
#include <QDateTime>
#include <QDebug>
#include <QUrl>
#include <ircmessage.h>
#include <irccommand.h>
#include <irc.h>

static QStringListModel* command_model = 0;
static const int VERTICAL_MARGIN = 1; // matches qlineedit_p.cpp

MessageView::MessageView(MessageView::ViewType type, Session* session, QWidget* parent) :
    QWidget(parent)
{
    d.setupUi(this);
    d.viewType = type;
    d.joined = false;

    d.topicLabel->setMinimumHeight(d.lineEditor->sizeHint().height());
    d.helpLabel->setMinimumHeight(d.lineEditor->sizeHint().height());

    connect(d.splitter, SIGNAL(splitterMoved(int, int)), this, SLOT(onSplitterMoved()));

    setFocusProxy(d.lineEditor);
    d.textBrowser->setBuddy(d.lineEditor);
    d.textBrowser->viewport()->installEventFilter(this);
    connect(d.textBrowser, SIGNAL(anchorClicked(QUrl)), SLOT(onAnchorClicked(QUrl)));

    d.formatter = new MessageFormatter(this);

    d.session = session;
    connect(d.session, SIGNAL(activeChanged(bool)), this, SIGNAL(activeChanged()));
    connect(&d.parser, SIGNAL(customCommand(QString, QStringList)), this, SLOT(onCustomCommand(QString, QStringList)));

    d.topicLabel->setVisible(type == ChannelView);
    d.listView->setVisible(type == ChannelView);
    if (type == ChannelView) {
        d.listView->setSession(session);
        connect(d.listView, SIGNAL(queried(QString)), this, SIGNAL(queried(QString)));
        connect(d.listView, SIGNAL(doubleClicked(QString)), this, SIGNAL(queried(QString)));
        connect(d.listView, SIGNAL(commandRequested(IrcCommand*)), d.session, SLOT(sendCommand(IrcCommand*)));
    }

    if (!command_model) {
        CommandParser::addCustomCommand("CLEAR", "");
        CommandParser::addCustomCommand("QUERY", "<user>");
        CommandParser::addCustomCommand("MSG", "<usr/channel> <message...>");

        QStringList prefixedCommands;
        foreach (const QString& command, CommandParser::availableCommands())
            prefixedCommands += "/" + command;

        command_model = new QStringListModel(qApp);
        command_model->setStringList(prefixedCommands);
    }

    d.lineEditor->completer()->setDefaultModel(d.listView->userModel());
    d.lineEditor->completer()->setSlashModel(command_model);

    connect(d.lineEditor, SIGNAL(send(QString)), this, SLOT(sendMessage(QString)));
    connect(d.lineEditor, SIGNAL(typed(QString)), this, SLOT(showHelp(QString)));

    d.helpLabel->hide();
    d.searchEditor->setTextEdit(d.textBrowser);

    QShortcut* shortcut = new QShortcut(Qt::Key_Escape, this);
    connect(shortcut, SIGNAL(activated()), this, SLOT(onEscPressed()));
}

MessageView::~MessageView()
{
}

bool MessageView::isActive() const
{
    if (!d.session->isActive())
        return false;
    if (d.viewType == ChannelView)
        return d.joined;
    return true;
}

MessageView::ViewType MessageView::viewType() const
{
    return d.viewType;
}

Session* MessageView::session() const
{
    return d.session;
}

UserModel* MessageView::userModel() const
{
    return d.listView->userModel();
}

QTextBrowser* MessageView::textBrowser() const
{
    return d.textBrowser;
}

MessageFormatter* MessageView::messageFormatter() const
{
    return d.formatter;
}

QString MessageView::receiver() const
{
    return d.receiver;
}

void MessageView::setReceiver(const QString& receiver)
{
    if (d.receiver != receiver) {
        d.receiver = receiver;
        if (d.viewType == ChannelView)
            d.listView->setChannel(receiver);
        emit receiverChanged(receiver);
    }
}

MenuFactory* MessageView::menuFactory() const
{
    return d.listView->menuFactory();
}

void MessageView::setMenuFactory(MenuFactory* factory)
{
    d.listView->setMenuFactory(factory);
}

QByteArray MessageView::saveSplitter() const
{
    if (d.viewType != ServerView)
        return d.splitter->saveState();
    return QByteArray();
}

void MessageView::restoreSplitter(const QByteArray& state)
{
    d.splitter->restoreState(state);
}

void MessageView::showHelp(const QString& text, bool error)
{
    QString syntax;
    if (text == "/") {
        QStringList commands = CommandParser::availableCommands();
        syntax = commands.join(" ");
    } else if (text.startsWith('/')) {
        QStringList words = text.mid(1).split(' ');
        QString command = words.value(0);
        QStringList suggestions = CommandParser::suggestedCommands(command, words.mid(1));
        if (suggestions.count() == 1)
            syntax = CommandParser::syntax(suggestions.first());
        else
            syntax = suggestions.join(" ");

        if (syntax.isEmpty() && error)
            syntax = tr("Unknown command '%1'").arg(command.toUpper());
    }

    d.helpLabel->setVisible(!syntax.isEmpty());
    QPalette pal;
    if (error)
        pal.setColor(QPalette::WindowText, d.settings.colors[Settings::Highlight]);
    d.helpLabel->setPalette(pal);
    d.helpLabel->setText(syntax);
}

void MessageView::sendMessage(const QString& message)
{
    QStringList lines = message.split(QRegExp("[\\r\\n]"), QString::SkipEmptyParts);
    foreach (const QString& line, lines) {
        IrcCommand* cmd = d.parser.parseCommand(receiver(), line);
        if (cmd) {
            d.session->sendCommand(cmd);

            if (cmd->type() == IrcCommand::Message || cmd->type() == IrcCommand::CtcpAction || cmd->type() == IrcCommand::Notice) {
                IrcMessage* msg = IrcMessage::fromData(":" + d.session->nickName().toUtf8() + " " + cmd->toString().toUtf8(), d.session);
                receiveMessage(msg);
                delete msg;
            }
        } else if (d.parser.hasError()) {
            showHelp(line, true);
        }
    }
}

void MessageView::appendMessage(const QString& message)
{
    if (!message.isEmpty()) {
        // workaround the link activation merge char format bug
        QString copy = message;
        if (copy.endsWith("</a>"))
            copy += " ";

        d.textBrowser->append(copy);
        if (!isVisible() && d.textBrowser->unseenBlock() == -1)
            d.textBrowser->setUnseenBlock(d.textBrowser->document()->blockCount() - 1);

#if QT_VERSION >= 0x040800
        QTextBlock block = d.textBrowser->document()->lastBlock();
        QTextBlockFormat format = block.blockFormat();
        format.setLineHeight(120, QTextBlockFormat::ProportionalHeight);
        QTextCursor cursor(block);
        cursor.setBlockFormat(format);
#endif // QT_VERSION
    }
}

void MessageView::hideEvent(QHideEvent* event)
{
    QWidget::hideEvent(event);
    d.textBrowser->setUnseenBlock(-1);
}

bool MessageView::eventFilter(QObject* object, QEvent* event)
{
    if (object == d.textBrowser->viewport() && event->type() == QEvent::ContextMenu) {
        QContextMenuEvent* menuEvent = static_cast<QContextMenuEvent*>(event);
        QAbstractTextDocumentLayout* layout = d.textBrowser->document()->documentLayout();
        QUrl link(layout->anchorAt(menuEvent->pos()));
        if (link.scheme() == "nick") {
            QMenu* standardMenu = d.textBrowser->createStandardContextMenu(menuEvent->pos());
            QMenu* customMenu = d.listView->menuFactory()->createUserViewMenu(link.toString(QUrl::RemoveScheme), this);
            customMenu->addSeparator();
            customMenu->insertActions(0, standardMenu->actions());
            customMenu->exec(menuEvent->globalPos());
            customMenu->deleteLater();
            delete standardMenu;
            return true;
        }
    }
    return QWidget::eventFilter(object, event);
}

void MessageView::onEscPressed()
{
    d.helpLabel->hide();
    d.searchEditor->hide();
    setFocus(Qt::OtherFocusReason);
}

void MessageView::onSplitterMoved()
{
    emit splitterChanged(d.splitter->saveState());
}

void MessageView::onAnchorClicked(const QUrl& link)
{
    if (link.scheme() == "nick")
        emit queried(link.toString(QUrl::RemoveScheme));
    else
        QDesktopServices::openUrl(link);
}

void MessageView::applySettings(const Settings& settings)
{
    d.formatter->setTimeStamp(settings.timeStamp);
    d.formatter->setTimeStampFormat(settings.timeStampFormat);
    d.formatter->setStripNicks(settings.stripNicks);

    if (!settings.font.isEmpty())
        d.textBrowser->setFont(settings.font);
    d.textBrowser->document()->setMaximumBlockCount(settings.maxBlockCount);

    QTextDocument* doc = d.topicLabel->findChild<QTextDocument*>();
    if (doc)
        doc->setDefaultStyleSheet(QString("a { color: %1 }").arg(settings.colors.value(Settings::Link)));

    QString backgroundColor = settings.colors.value(Settings::Background);
    d.textBrowser->setStyleSheet(QString("QTextBrowser { background-color: %1 }").arg(backgroundColor));

    d.textBrowser->document()->setDefaultStyleSheet(
        QString(
            ".highlight { color: %1 }"
            ".message   { color: %2 }"
            ".notice    { color: %3 }"
            ".action    { color: %4 }"
            ".event     { color: %5 }"
            ".timestamp { color: %6; font-size: small }"
            "a { color: %7 }"
        ).arg(settings.colors.value(Settings::Highlight))
        .arg(settings.colors.value(Settings::Message))
        .arg(settings.colors.value(Settings::Notice))
        .arg(settings.colors.value(Settings::Action))
        .arg(settings.colors.value(Settings::Event))
        .arg(settings.colors.value(Settings::TimeStamp))
        .arg(settings.colors.value(Settings::Link)));
    d.settings = settings;
}

void MessageView::receiveMessage(IrcMessage* message)
{
    if (d.viewType == ChannelView)
        d.listView->processMessage(message);

    bool ignore = false;
    switch (message->type()) {
        case IrcMessage::Private: {
            IrcSender sender = message->sender();
            if (sender.name() == QLatin1String("***") && sender.user() == QLatin1String("znc")) {
                QString content = static_cast<IrcPrivateMessage*>(message)->message();
                if (content == QLatin1String("Buffer Playback...")) {
                    d.formatter->setZncPlaybackMode(true);
                    ignore = true;
                } else if (content == QLatin1String("Playback Complete.")) {
                    d.formatter->setZncPlaybackMode(false);
                    ignore = true;
                }
            }
            break;
        }
        case IrcMessage::Topic:
            d.topicLabel->setText(d.formatter->formatHtml(static_cast<IrcTopicMessage*>(message)->topic()));
            if (d.topicLabel->text().isEmpty())
                d.topicLabel->setText(tr("-"));
            break;
        case IrcMessage::Unknown:
            qWarning() << "unknown:" << message;
            break;
        case IrcMessage::Join:
            if (message->flags() & IrcMessage::Own) {
                d.joined = true;
                emit activeChanged();
            }
            break;
        case IrcMessage::Part:
            if (message->flags() & IrcMessage::Own) {
                d.joined = false;
                emit activeChanged();
            }
            break;
        case IrcMessage::Numeric:
            switch (static_cast<IrcNumericMessage*>(message)->code()) {
                case Irc::RPL_NOTOPIC:
                    d.topicLabel->setText(tr("-"));
                    break;
                case Irc::RPL_TOPIC:
                    d.topicLabel->setText(d.formatter->formatHtml(message->parameters().value(2)));
                    break;
                case Irc::RPL_TOPICWHOTIME: {
                    QDateTime dateTime = QDateTime::fromTime_t(message->parameters().value(3).toInt());
                    d.topicLabel->setToolTip(tr("Set %1 by %2").arg(dateTime.toString(), message->parameters().value(2)));
                    break;
                }
                default:
                    break;
            }
            break;
        default:
            break;
    }

    d.formatter->setHighlights(QStringList() << d.session->nickName());
    QString formatted = d.formatter->formatMessage(message, d.listView->userModel());
    if (formatted.length()) {
        if (!ignore && (!isVisible() || !isActiveWindow())) {
            IrcMessage::Type type = d.formatter->effectiveMessageType();
            if (d.formatter->hasHighlight() || (type == IrcMessage::Private && d.viewType != ChannelView))
                emit highlighted(message);
            else if (type == IrcMessage::Notice || type == IrcMessage::Private) // TODO: || (!d.receivedCodes.contains(Irc::RPL_ENDOFMOTD) && d.viewType == ServerView))
                emit missed(message);
        }

        appendMessage(formatted);
    }
}

bool MessageView::hasUser(const QString& user) const
{
    return (!d.session->nickName().compare(user, Qt::CaseInsensitive)) ||
           (d.viewType == QueryView && !d.receiver.compare(user, Qt::CaseInsensitive)) ||
           (d.viewType == ChannelView && d.listView->hasUser(user));
}

void MessageView::onCustomCommand(const QString& command, const QStringList& params)
{
    if (command == "CLEAR")
        d.textBrowser->clear();
    else if (command == "MSG")
        emit messaged(params.value(0), QStringList(params.mid(1)).join(" "));
    else if (command == "QUERY")
        emit queried(params.value(0));
}
<|endoftext|>