First Commit
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
CPlusPlusCodeTokeniser::CPlusPlusCodeTokeniser() {}
|
||||
CPlusPlusCodeTokeniser::~CPlusPlusCodeTokeniser() {}
|
||||
|
||||
int CPlusPlusCodeTokeniser::readNextToken (CodeDocument::Iterator& source)
|
||||
{
|
||||
return CppTokeniserFunctions::readNextToken (source);
|
||||
}
|
||||
|
||||
CodeEditorComponent::ColourScheme CPlusPlusCodeTokeniser::getDefaultColourScheme()
|
||||
{
|
||||
struct Type
|
||||
{
|
||||
const char* name;
|
||||
uint32 colour;
|
||||
};
|
||||
|
||||
const Type types[] =
|
||||
{
|
||||
{ "Error", 0xffcc0000 },
|
||||
{ "Comment", 0xff00aa00 },
|
||||
{ "Keyword", 0xff0000cc },
|
||||
{ "Operator", 0xff225500 },
|
||||
{ "Identifier", 0xff000000 },
|
||||
{ "Integer", 0xff880000 },
|
||||
{ "Float", 0xff885500 },
|
||||
{ "String", 0xff990099 },
|
||||
{ "Bracket", 0xff000055 },
|
||||
{ "Punctuation", 0xff004400 },
|
||||
{ "Preprocessor Text", 0xff660000 }
|
||||
};
|
||||
|
||||
CodeEditorComponent::ColourScheme cs;
|
||||
|
||||
for (auto& t : types)
|
||||
cs.set (t.name, Colour (t.colour));
|
||||
|
||||
return cs;
|
||||
}
|
||||
|
||||
bool CPlusPlusCodeTokeniser::isReservedKeyword (const String& token) noexcept
|
||||
{
|
||||
return CppTokeniserFunctions::isReservedKeyword (token.getCharPointer(), token.length());
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A simple lexical analyser for syntax colouring of C++ code.
|
||||
|
||||
@see CodeEditorComponent, CodeDocument
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API CPlusPlusCodeTokeniser : public CodeTokeniser
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
CPlusPlusCodeTokeniser();
|
||||
~CPlusPlusCodeTokeniser() override;
|
||||
|
||||
//==============================================================================
|
||||
int readNextToken (CodeDocument::Iterator&) override;
|
||||
CodeEditorComponent::ColourScheme getDefaultColourScheme() override;
|
||||
|
||||
/** This is a handy method for checking whether a string is a c++ reserved keyword. */
|
||||
static bool isReservedKeyword (const String& token) noexcept;
|
||||
|
||||
/** The token values returned by this tokeniser. */
|
||||
enum TokenType
|
||||
{
|
||||
tokenType_error = 0,
|
||||
tokenType_comment,
|
||||
tokenType_keyword,
|
||||
tokenType_operator,
|
||||
tokenType_identifier,
|
||||
tokenType_integer,
|
||||
tokenType_float,
|
||||
tokenType_string,
|
||||
tokenType_bracket,
|
||||
tokenType_punctuation,
|
||||
tokenType_preprocessor
|
||||
};
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
JUCE_LEAK_DETECTOR (CPlusPlusCodeTokeniser)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+683
@@ -0,0 +1,683 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/** Class containing some basic functions for simple tokenising of C++ code.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
struct CppTokeniserFunctions
|
||||
{
|
||||
static bool isIdentifierStart (const juce_wchar c) noexcept
|
||||
{
|
||||
return CharacterFunctions::isLetter (c)
|
||||
|| c == '_' || c == '@';
|
||||
}
|
||||
|
||||
static bool isIdentifierBody (const juce_wchar c) noexcept
|
||||
{
|
||||
return CharacterFunctions::isLetterOrDigit (c)
|
||||
|| c == '_' || c == '@';
|
||||
}
|
||||
|
||||
static bool isReservedKeyword (String::CharPointerType token, const int tokenLength) noexcept
|
||||
{
|
||||
static const char* const keywords2Char[] =
|
||||
{ "do", "if", "or", nullptr };
|
||||
|
||||
static const char* const keywords3Char[] =
|
||||
{ "and", "asm", "for", "int", "new", "not", "try", "xor", nullptr };
|
||||
|
||||
static const char* const keywords4Char[] =
|
||||
{ "auto", "bool", "case", "char", "else", "enum", "goto",
|
||||
"long", "this", "true", "void", nullptr };
|
||||
|
||||
static const char* const keywords5Char[] =
|
||||
{ "bitor", "break", "catch", "class", "compl", "const", "false", "final",
|
||||
"float", "or_eq", "short", "throw", "union", "using", "while", nullptr };
|
||||
|
||||
static const char* const keywords6Char[] =
|
||||
{ "and_eq", "bitand", "delete", "double", "export", "extern", "friend",
|
||||
"import", "inline", "module", "not_eq", "public", "return", "signed",
|
||||
"sizeof", "static", "struct", "switch", "typeid", "xor_eq", nullptr };
|
||||
|
||||
static const char* const keywords7Char[] =
|
||||
{ "__cdecl", "_Pragma", "alignas", "alignof", "concept", "default",
|
||||
"mutable", "nullptr", "private", "typedef", "uint8_t", "virtual",
|
||||
"wchar_t", nullptr };
|
||||
|
||||
static const char* const keywordsOther[] =
|
||||
{ "@class", "@dynamic", "@end", "@implementation", "@interface", "@public",
|
||||
"@private", "@protected", "@property", "@synthesize", "__fastcall", "__stdcall",
|
||||
"atomic_cancel", "atomic_commit", "atomic_noexcept", "char16_t", "char32_t",
|
||||
"co_await", "co_return", "co_yield", "const_cast", "constexpr", "continue",
|
||||
"decltype", "dynamic_cast", "explicit", "namespace", "noexcept", "operator", "override",
|
||||
"protected", "register", "reinterpret_cast", "requires", "static_assert",
|
||||
"static_cast", "synchronized", "template", "thread_local", "typename", "unsigned",
|
||||
"volatile", nullptr };
|
||||
|
||||
const char* const* k;
|
||||
|
||||
switch (tokenLength)
|
||||
{
|
||||
case 2: k = keywords2Char; break;
|
||||
case 3: k = keywords3Char; break;
|
||||
case 4: k = keywords4Char; break;
|
||||
case 5: k = keywords5Char; break;
|
||||
case 6: k = keywords6Char; break;
|
||||
case 7: k = keywords7Char; break;
|
||||
|
||||
default:
|
||||
if (tokenLength < 2 || tokenLength > 16)
|
||||
return false;
|
||||
|
||||
k = keywordsOther;
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; k[i] != nullptr; ++i)
|
||||
if (token.compare (CharPointer_ASCII (k[i])) == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static int parseIdentifier (Iterator& source) noexcept
|
||||
{
|
||||
int tokenLength = 0;
|
||||
String::CharPointerType::CharType possibleIdentifier[100] = {};
|
||||
String::CharPointerType possible (possibleIdentifier);
|
||||
|
||||
while (isIdentifierBody (source.peekNextChar()))
|
||||
{
|
||||
auto c = source.nextChar();
|
||||
|
||||
if (tokenLength < 20)
|
||||
possible.write (c);
|
||||
|
||||
++tokenLength;
|
||||
}
|
||||
|
||||
if (tokenLength > 1 && tokenLength <= 16)
|
||||
{
|
||||
possible.writeNull();
|
||||
|
||||
if (isReservedKeyword (String::CharPointerType (possibleIdentifier), tokenLength))
|
||||
return CPlusPlusCodeTokeniser::tokenType_keyword;
|
||||
}
|
||||
|
||||
return CPlusPlusCodeTokeniser::tokenType_identifier;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static bool skipNumberSuffix (Iterator& source)
|
||||
{
|
||||
auto c = source.peekNextChar();
|
||||
|
||||
if (c == 'l' || c == 'L' || c == 'u' || c == 'U')
|
||||
source.skip();
|
||||
|
||||
if (CharacterFunctions::isLetterOrDigit (source.peekNextChar()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isHexDigit (const juce_wchar c) noexcept
|
||||
{
|
||||
return (c >= '0' && c <= '9')
|
||||
|| (c >= 'a' && c <= 'f')
|
||||
|| (c >= 'A' && c <= 'F');
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static bool parseHexLiteral (Iterator& source) noexcept
|
||||
{
|
||||
if (source.peekNextChar() == '-')
|
||||
source.skip();
|
||||
|
||||
if (source.nextChar() != '0')
|
||||
return false;
|
||||
|
||||
auto c = source.nextChar();
|
||||
|
||||
if (c != 'x' && c != 'X')
|
||||
return false;
|
||||
|
||||
int numDigits = 0;
|
||||
|
||||
while (isHexDigit (source.peekNextChar()))
|
||||
{
|
||||
++numDigits;
|
||||
source.skip();
|
||||
}
|
||||
|
||||
if (numDigits == 0)
|
||||
return false;
|
||||
|
||||
return skipNumberSuffix (source);
|
||||
}
|
||||
|
||||
static bool isOctalDigit (const juce_wchar c) noexcept
|
||||
{
|
||||
return c >= '0' && c <= '7';
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static bool parseOctalLiteral (Iterator& source) noexcept
|
||||
{
|
||||
if (source.peekNextChar() == '-')
|
||||
source.skip();
|
||||
|
||||
if (source.nextChar() != '0')
|
||||
return false;
|
||||
|
||||
if (! isOctalDigit (source.nextChar()))
|
||||
return false;
|
||||
|
||||
while (isOctalDigit (source.peekNextChar()))
|
||||
source.skip();
|
||||
|
||||
return skipNumberSuffix (source);
|
||||
}
|
||||
|
||||
static bool isDecimalDigit (const juce_wchar c) noexcept
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static bool parseDecimalLiteral (Iterator& source) noexcept
|
||||
{
|
||||
if (source.peekNextChar() == '-')
|
||||
source.skip();
|
||||
|
||||
int numChars = 0;
|
||||
while (isDecimalDigit (source.peekNextChar()))
|
||||
{
|
||||
++numChars;
|
||||
source.skip();
|
||||
}
|
||||
|
||||
if (numChars == 0)
|
||||
return false;
|
||||
|
||||
return skipNumberSuffix (source);
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static bool parseFloatLiteral (Iterator& source) noexcept
|
||||
{
|
||||
if (source.peekNextChar() == '-')
|
||||
source.skip();
|
||||
|
||||
int numDigits = 0;
|
||||
|
||||
while (isDecimalDigit (source.peekNextChar()))
|
||||
{
|
||||
source.skip();
|
||||
++numDigits;
|
||||
}
|
||||
|
||||
const bool hasPoint = (source.peekNextChar() == '.');
|
||||
|
||||
if (hasPoint)
|
||||
{
|
||||
source.skip();
|
||||
|
||||
while (isDecimalDigit (source.peekNextChar()))
|
||||
{
|
||||
source.skip();
|
||||
++numDigits;
|
||||
}
|
||||
}
|
||||
|
||||
if (numDigits == 0)
|
||||
return false;
|
||||
|
||||
auto c = source.peekNextChar();
|
||||
bool hasExponent = (c == 'e' || c == 'E');
|
||||
|
||||
if (hasExponent)
|
||||
{
|
||||
source.skip();
|
||||
c = source.peekNextChar();
|
||||
|
||||
if (c == '+' || c == '-')
|
||||
source.skip();
|
||||
|
||||
int numExpDigits = 0;
|
||||
|
||||
while (isDecimalDigit (source.peekNextChar()))
|
||||
{
|
||||
source.skip();
|
||||
++numExpDigits;
|
||||
}
|
||||
|
||||
if (numExpDigits == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
c = source.peekNextChar();
|
||||
|
||||
if (c == 'f' || c == 'F')
|
||||
source.skip();
|
||||
else if (! (hasExponent || hasPoint))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static int parseNumber (Iterator& source)
|
||||
{
|
||||
const Iterator original (source);
|
||||
|
||||
if (parseFloatLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_float;
|
||||
source = original;
|
||||
|
||||
if (parseHexLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_integer;
|
||||
source = original;
|
||||
|
||||
if (parseOctalLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_integer;
|
||||
source = original;
|
||||
|
||||
if (parseDecimalLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_integer;
|
||||
source = original;
|
||||
|
||||
return CPlusPlusCodeTokeniser::tokenType_error;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static void skipQuotedString (Iterator& source) noexcept
|
||||
{
|
||||
auto quote = source.nextChar();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto c = source.nextChar();
|
||||
|
||||
if (c == quote || c == 0)
|
||||
break;
|
||||
|
||||
if (c == '\\')
|
||||
source.skip();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static void skipComment (Iterator& source) noexcept
|
||||
{
|
||||
bool lastWasStar = false;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto c = source.nextChar();
|
||||
|
||||
if (c == 0 || (c == '/' && lastWasStar))
|
||||
break;
|
||||
|
||||
lastWasStar = (c == '*');
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static void skipPreprocessorLine (Iterator& source) noexcept
|
||||
{
|
||||
bool lastWasBackslash = false;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto c = source.peekNextChar();
|
||||
|
||||
if (c == '"')
|
||||
{
|
||||
skipQuotedString (source);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '/')
|
||||
{
|
||||
Iterator next (source);
|
||||
next.skip();
|
||||
auto c2 = next.peekNextChar();
|
||||
|
||||
if (c2 == '/' || c2 == '*')
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == 0)
|
||||
break;
|
||||
|
||||
if (c == '\n' || c == '\r')
|
||||
{
|
||||
source.skipToEndOfLine();
|
||||
|
||||
if (lastWasBackslash)
|
||||
skipPreprocessorLine (source);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
lastWasBackslash = (c == '\\');
|
||||
source.skip();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static void skipIfNextCharMatches (Iterator& source, const juce_wchar c) noexcept
|
||||
{
|
||||
if (source.peekNextChar() == c)
|
||||
source.skip();
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static void skipIfNextCharMatches (Iterator& source, const juce_wchar c1, const juce_wchar c2) noexcept
|
||||
{
|
||||
auto c = source.peekNextChar();
|
||||
|
||||
if (c == c1 || c == c2)
|
||||
source.skip();
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static int readNextToken (Iterator& source)
|
||||
{
|
||||
source.skipWhitespace();
|
||||
auto firstChar = source.peekNextChar();
|
||||
|
||||
switch (firstChar)
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
case '.':
|
||||
{
|
||||
auto result = parseNumber (source);
|
||||
|
||||
if (result == CPlusPlusCodeTokeniser::tokenType_error)
|
||||
{
|
||||
source.skip();
|
||||
|
||||
if (firstChar == '.')
|
||||
return CPlusPlusCodeTokeniser::tokenType_punctuation;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case ',':
|
||||
case ';':
|
||||
case ':':
|
||||
source.skip();
|
||||
return CPlusPlusCodeTokeniser::tokenType_punctuation;
|
||||
|
||||
case '(': case ')':
|
||||
case '{': case '}':
|
||||
case '[': case ']':
|
||||
source.skip();
|
||||
return CPlusPlusCodeTokeniser::tokenType_bracket;
|
||||
|
||||
case '"':
|
||||
case '\'':
|
||||
skipQuotedString (source);
|
||||
return CPlusPlusCodeTokeniser::tokenType_string;
|
||||
|
||||
case '+':
|
||||
source.skip();
|
||||
skipIfNextCharMatches (source, '+', '=');
|
||||
return CPlusPlusCodeTokeniser::tokenType_operator;
|
||||
|
||||
case '-':
|
||||
{
|
||||
source.skip();
|
||||
auto result = parseNumber (source);
|
||||
|
||||
if (result == CPlusPlusCodeTokeniser::tokenType_error)
|
||||
{
|
||||
skipIfNextCharMatches (source, '-', '=');
|
||||
return CPlusPlusCodeTokeniser::tokenType_operator;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case '*': case '%':
|
||||
case '=': case '!':
|
||||
source.skip();
|
||||
skipIfNextCharMatches (source, '=');
|
||||
return CPlusPlusCodeTokeniser::tokenType_operator;
|
||||
|
||||
case '/':
|
||||
{
|
||||
source.skip();
|
||||
auto nextChar = source.peekNextChar();
|
||||
|
||||
if (nextChar == '/')
|
||||
{
|
||||
source.skipToEndOfLine();
|
||||
return CPlusPlusCodeTokeniser::tokenType_comment;
|
||||
}
|
||||
|
||||
if (nextChar == '*')
|
||||
{
|
||||
source.skip();
|
||||
skipComment (source);
|
||||
return CPlusPlusCodeTokeniser::tokenType_comment;
|
||||
}
|
||||
|
||||
if (nextChar == '=')
|
||||
source.skip();
|
||||
|
||||
return CPlusPlusCodeTokeniser::tokenType_operator;
|
||||
}
|
||||
|
||||
case '?':
|
||||
case '~':
|
||||
source.skip();
|
||||
return CPlusPlusCodeTokeniser::tokenType_operator;
|
||||
|
||||
case '<': case '>':
|
||||
case '|': case '&': case '^':
|
||||
source.skip();
|
||||
skipIfNextCharMatches (source, firstChar);
|
||||
skipIfNextCharMatches (source, '=');
|
||||
return CPlusPlusCodeTokeniser::tokenType_operator;
|
||||
|
||||
case '#':
|
||||
skipPreprocessorLine (source);
|
||||
return CPlusPlusCodeTokeniser::tokenType_preprocessor;
|
||||
|
||||
default:
|
||||
if (isIdentifierStart (firstChar))
|
||||
return parseIdentifier (source);
|
||||
|
||||
source.skip();
|
||||
break;
|
||||
}
|
||||
|
||||
return CPlusPlusCodeTokeniser::tokenType_error;
|
||||
}
|
||||
|
||||
/** A class that can be passed to the CppTokeniserFunctions functions in order to
|
||||
parse a String.
|
||||
*/
|
||||
struct StringIterator
|
||||
{
|
||||
StringIterator (const String& s) noexcept : t (s.getCharPointer()) {}
|
||||
StringIterator (String::CharPointerType s) noexcept : t (s) {}
|
||||
|
||||
juce_wchar nextChar() noexcept { if (isEOF()) return 0; ++numChars; return t.getAndAdvance(); }
|
||||
juce_wchar peekNextChar()noexcept { return *t; }
|
||||
void skip() noexcept { if (! isEOF()) { ++t; ++numChars; } }
|
||||
void skipWhitespace() noexcept { while (t.isWhitespace()) skip(); }
|
||||
void skipToEndOfLine() noexcept { while (*t != '\r' && *t != '\n' && *t != 0) skip(); }
|
||||
bool isEOF() const noexcept { return t.isEmpty(); }
|
||||
|
||||
String::CharPointerType t;
|
||||
int numChars = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Takes a UTF8 string and writes it to a stream using standard C++ escape sequences for any
|
||||
non-ascii bytes.
|
||||
|
||||
Although not strictly a tokenising function, this is still a function that often comes in
|
||||
handy when working with C++ code!
|
||||
|
||||
Note that addEscapeChars() is easier to use than this function if you're working with Strings.
|
||||
|
||||
@see addEscapeChars
|
||||
*/
|
||||
static void writeEscapeChars (OutputStream& out, const char* utf8, const int numBytesToRead,
|
||||
const int maxCharsOnLine, const bool breakAtNewLines,
|
||||
const bool replaceSingleQuotes, const bool allowStringBreaks)
|
||||
{
|
||||
int charsOnLine = 0;
|
||||
bool lastWasHexEscapeCode = false;
|
||||
bool trigraphDetected = false;
|
||||
|
||||
for (int i = 0; i < numBytesToRead || numBytesToRead < 0; ++i)
|
||||
{
|
||||
auto c = (unsigned char) utf8[i];
|
||||
bool startNewLine = false;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
|
||||
case '\t': out << "\\t"; trigraphDetected = false; lastWasHexEscapeCode = false; charsOnLine += 2; break;
|
||||
case '\r': out << "\\r"; trigraphDetected = false; lastWasHexEscapeCode = false; charsOnLine += 2; break;
|
||||
case '\n': out << "\\n"; trigraphDetected = false; lastWasHexEscapeCode = false; charsOnLine += 2; startNewLine = breakAtNewLines; break;
|
||||
case '\\': out << "\\\\"; trigraphDetected = false; lastWasHexEscapeCode = false; charsOnLine += 2; break;
|
||||
case '\"': out << "\\\""; trigraphDetected = false; lastWasHexEscapeCode = false; charsOnLine += 2; break;
|
||||
|
||||
case '?':
|
||||
if (trigraphDetected)
|
||||
{
|
||||
out << "\\?";
|
||||
charsOnLine++;
|
||||
trigraphDetected = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
out << "?";
|
||||
trigraphDetected = true;
|
||||
}
|
||||
|
||||
lastWasHexEscapeCode = false;
|
||||
charsOnLine++;
|
||||
break;
|
||||
|
||||
case 0:
|
||||
if (numBytesToRead < 0)
|
||||
return;
|
||||
|
||||
out << "\\0";
|
||||
lastWasHexEscapeCode = true;
|
||||
trigraphDetected = false;
|
||||
charsOnLine += 2;
|
||||
break;
|
||||
|
||||
case '\'':
|
||||
if (replaceSingleQuotes)
|
||||
{
|
||||
out << "\\\'";
|
||||
lastWasHexEscapeCode = false;
|
||||
trigraphDetected = false;
|
||||
charsOnLine += 2;
|
||||
break;
|
||||
}
|
||||
// deliberate fall-through...
|
||||
JUCE_FALLTHROUGH
|
||||
|
||||
default:
|
||||
if (c >= 32 && c < 127 && ! (lastWasHexEscapeCode // (have to avoid following a hex escape sequence with a valid hex digit)
|
||||
&& CharacterFunctions::getHexDigitValue (c) >= 0))
|
||||
{
|
||||
out << (char) c;
|
||||
lastWasHexEscapeCode = false;
|
||||
trigraphDetected = false;
|
||||
++charsOnLine;
|
||||
}
|
||||
else if (allowStringBreaks && lastWasHexEscapeCode && c >= 32 && c < 127)
|
||||
{
|
||||
out << "\"\"" << (char) c;
|
||||
lastWasHexEscapeCode = false;
|
||||
trigraphDetected = false;
|
||||
charsOnLine += 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
out << (c < 16 ? "\\x0" : "\\x") << String::toHexString ((int) c);
|
||||
lastWasHexEscapeCode = true;
|
||||
trigraphDetected = false;
|
||||
charsOnLine += 4;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ((startNewLine || (maxCharsOnLine > 0 && charsOnLine >= maxCharsOnLine))
|
||||
&& (numBytesToRead < 0 || i < numBytesToRead - 1))
|
||||
{
|
||||
charsOnLine = 0;
|
||||
out << "\"" << newLine << "\"";
|
||||
lastWasHexEscapeCode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Takes a string and returns a version of it where standard C++ escape sequences have been
|
||||
used to replace any non-ascii bytes.
|
||||
|
||||
Although not strictly a tokenising function, this is still a function that often comes in
|
||||
handy when working with C++ code!
|
||||
|
||||
@see writeEscapeChars
|
||||
*/
|
||||
static String addEscapeChars (const String& s)
|
||||
{
|
||||
MemoryOutputStream mo;
|
||||
writeEscapeChars (mo, s.toRawUTF8(), -1, -1, false, true, true);
|
||||
return mo.toString();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,459 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class CodeDocumentLine;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class for storing and manipulating a source code file.
|
||||
|
||||
When using a CodeEditorComponent, it takes one of these as its source object.
|
||||
|
||||
The CodeDocument stores its content as an array of lines, which makes it
|
||||
quick to insert and delete.
|
||||
|
||||
@see CodeEditorComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API CodeDocument
|
||||
{
|
||||
public:
|
||||
/** Creates a new, empty document. */
|
||||
CodeDocument();
|
||||
|
||||
/** Destructor. */
|
||||
~CodeDocument();
|
||||
|
||||
//==============================================================================
|
||||
/** A position in a code document.
|
||||
|
||||
Using this class you can find a position in a code document and quickly get its
|
||||
character position, line, and index. By calling setPositionMaintained (true), the
|
||||
position is automatically updated when text is inserted or deleted in the document,
|
||||
so that it maintains its original place in the text.
|
||||
*/
|
||||
class JUCE_API Position
|
||||
{
|
||||
public:
|
||||
/** Creates an uninitialised position.
|
||||
Don't attempt to call any methods on this until you've given it an owner document
|
||||
to refer to!
|
||||
*/
|
||||
Position() noexcept;
|
||||
|
||||
/** Creates a position based on a line and index in a document.
|
||||
|
||||
Note that this index is NOT the column number, it's the number of characters from the
|
||||
start of the line. The "column" number isn't quite the same, because if the line
|
||||
contains any tab characters, the relationship of the index to its visual column depends on
|
||||
the number of spaces per tab being used!
|
||||
|
||||
Lines are numbered from zero, and if the line or index are beyond the bounds of the document,
|
||||
they will be adjusted to keep them within its limits.
|
||||
*/
|
||||
Position (const CodeDocument& ownerDocument,
|
||||
int line, int indexInLine) noexcept;
|
||||
|
||||
/** Creates a position based on a character index in a document.
|
||||
This position is placed at the specified number of characters from the start of the
|
||||
document. The line and column are auto-calculated.
|
||||
|
||||
If the position is beyond the range of the document, it'll be adjusted to keep it
|
||||
inside.
|
||||
*/
|
||||
Position (const CodeDocument& ownerDocument,
|
||||
int charactersFromStartOfDocument) noexcept;
|
||||
|
||||
/** Creates a copy of another position.
|
||||
|
||||
This will copy the position, but the new object will not be set to maintain its position,
|
||||
even if the source object was set to do so.
|
||||
*/
|
||||
Position (const Position&) noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
~Position();
|
||||
|
||||
Position& operator= (const Position&);
|
||||
|
||||
bool operator== (const Position&) const noexcept;
|
||||
bool operator!= (const Position&) const noexcept;
|
||||
|
||||
/** Points this object at a new position within the document.
|
||||
|
||||
If the position is beyond the range of the document, it'll be adjusted to keep it
|
||||
inside.
|
||||
@see getPosition, setLineAndIndex
|
||||
*/
|
||||
void setPosition (int charactersFromStartOfDocument);
|
||||
|
||||
/** Returns the position as the number of characters from the start of the document.
|
||||
@see setPosition, getLineNumber, getIndexInLine
|
||||
*/
|
||||
int getPosition() const noexcept { return characterPos; }
|
||||
|
||||
/** Moves the position to a new line and index within the line.
|
||||
|
||||
Note that the index is NOT the column at which the position appears in an editor.
|
||||
If the line contains any tab characters, the relationship of the index to its
|
||||
visual position depends on the number of spaces per tab being used!
|
||||
|
||||
Lines are numbered from zero, and if the line or index are beyond the bounds of the document,
|
||||
they will be adjusted to keep them within its limits.
|
||||
*/
|
||||
void setLineAndIndex (int newLineNumber, int newIndexInLine);
|
||||
|
||||
/** Returns the line number of this position.
|
||||
The first line in the document is numbered zero, not one!
|
||||
*/
|
||||
int getLineNumber() const noexcept { return line; }
|
||||
|
||||
/** Returns the number of characters from the start of the line.
|
||||
|
||||
Note that this value is NOT the column at which the position appears in an editor.
|
||||
If the line contains any tab characters, the relationship of the index to its
|
||||
visual position depends on the number of spaces per tab being used!
|
||||
*/
|
||||
int getIndexInLine() const noexcept { return indexInLine; }
|
||||
|
||||
/** Allows the position to be automatically updated when the document changes.
|
||||
|
||||
If this is set to true, the position will register with its document so that
|
||||
when the document has text inserted or deleted, this position will be automatically
|
||||
moved to keep it at the same position in the text.
|
||||
*/
|
||||
void setPositionMaintained (bool isMaintained);
|
||||
|
||||
//==============================================================================
|
||||
/** Moves the position forwards or backwards by the specified number of characters.
|
||||
@see movedBy
|
||||
*/
|
||||
void moveBy (int characterDelta);
|
||||
|
||||
/** Returns a position which is the same as this one, moved by the specified number of
|
||||
characters.
|
||||
@see moveBy
|
||||
*/
|
||||
Position movedBy (int characterDelta) const;
|
||||
|
||||
/** Returns a position which is the same as this one, moved up or down by the specified
|
||||
number of lines.
|
||||
@see movedBy
|
||||
*/
|
||||
Position movedByLines (int deltaLines) const;
|
||||
|
||||
/** Returns the character in the document at this position.
|
||||
@see getLineText
|
||||
*/
|
||||
juce_wchar getCharacter() const;
|
||||
|
||||
/** Returns the line from the document that this position is within.
|
||||
@see getCharacter, getLineNumber
|
||||
*/
|
||||
String getLineText() const;
|
||||
|
||||
private:
|
||||
CodeDocument* owner = nullptr;
|
||||
int characterPos = 0, line = 0, indexInLine = 0;
|
||||
bool positionMaintained = false;
|
||||
|
||||
friend class CodeDocument;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the full text of the document. */
|
||||
String getAllContent() const;
|
||||
|
||||
/** Returns a section of the document's text. */
|
||||
String getTextBetween (const Position& start, const Position& end) const;
|
||||
|
||||
/** Returns a line from the document. */
|
||||
String getLine (int lineIndex) const noexcept;
|
||||
|
||||
/** Returns the number of characters in the document. */
|
||||
int getNumCharacters() const noexcept;
|
||||
|
||||
/** Returns the number of lines in the document. */
|
||||
int getNumLines() const noexcept { return lines.size(); }
|
||||
|
||||
/** Returns the number of characters in the longest line of the document. */
|
||||
int getMaximumLineLength() noexcept;
|
||||
|
||||
/** Deletes a section of the text.
|
||||
This operation is undoable.
|
||||
*/
|
||||
void deleteSection (const Position& startPosition, const Position& endPosition);
|
||||
|
||||
/** Deletes a section of the text.
|
||||
This operation is undoable.
|
||||
*/
|
||||
void deleteSection (int startIndex, int endIndex);
|
||||
|
||||
/** Inserts some text into the document at a given position.
|
||||
This operation is undoable.
|
||||
*/
|
||||
void insertText (const Position& position, const String& text);
|
||||
|
||||
/** Inserts some text into the document at a given position.
|
||||
This operation is undoable.
|
||||
*/
|
||||
void insertText (int insertIndex, const String& text);
|
||||
|
||||
/** Replaces a section of the text with a new string.
|
||||
This operation is undoable.
|
||||
*/
|
||||
void replaceSection (int startIndex, int endIndex, const String& newText);
|
||||
|
||||
/** Clears the document and replaces it with some new text.
|
||||
|
||||
This operation is undoable - if you're trying to completely reset the document, you
|
||||
might want to also call clearUndoHistory() and setSavePoint() after using this method.
|
||||
*/
|
||||
void replaceAllContent (const String& newContent);
|
||||
|
||||
/** Analyses the changes between the current content and some new text, and applies
|
||||
those changes.
|
||||
*/
|
||||
void applyChanges (const String& newContent);
|
||||
|
||||
/** Replaces the editor's contents with the contents of a stream.
|
||||
This will also reset the undo history and save point marker.
|
||||
*/
|
||||
bool loadFromStream (InputStream& stream);
|
||||
|
||||
/** Writes the editor's current contents to a stream. */
|
||||
bool writeToStream (OutputStream& stream);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the preferred new-line characters for the document.
|
||||
This will be either "\\n", "\\r\\n", or (rarely) "\\r".
|
||||
@see setNewLineCharacters
|
||||
*/
|
||||
String getNewLineCharacters() const noexcept { return newLineChars; }
|
||||
|
||||
/** Sets the new-line characters that the document should use.
|
||||
The string must be either "\\n", "\\r\\n", or (rarely) "\\r".
|
||||
@see getNewLineCharacters
|
||||
*/
|
||||
void setNewLineCharacters (const String& newLineCharacters) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Begins a new undo transaction.
|
||||
|
||||
The document itself will not call this internally, so relies on whatever is using the
|
||||
document to periodically call this to break up the undo sequence into sensible chunks.
|
||||
@see UndoManager::beginNewTransaction
|
||||
*/
|
||||
void newTransaction();
|
||||
|
||||
/** Undo the last operation.
|
||||
@see UndoManager::undo
|
||||
*/
|
||||
void undo();
|
||||
|
||||
/** Redo the last operation.
|
||||
@see UndoManager::redo
|
||||
*/
|
||||
void redo();
|
||||
|
||||
/** Clears the undo history.
|
||||
@see UndoManager::clearUndoHistory
|
||||
*/
|
||||
void clearUndoHistory();
|
||||
|
||||
/** Returns the document's UndoManager */
|
||||
UndoManager& getUndoManager() noexcept { return undoManager; }
|
||||
|
||||
//==============================================================================
|
||||
/** Makes a note that the document's current state matches the one that is saved.
|
||||
|
||||
After this has been called, hasChangedSinceSavePoint() will return false until
|
||||
the document has been altered, and then it'll start returning true. If the document is
|
||||
altered, but then undone until it gets back to this state, hasChangedSinceSavePoint()
|
||||
will again return false.
|
||||
|
||||
@see hasChangedSinceSavePoint
|
||||
*/
|
||||
void setSavePoint() noexcept;
|
||||
|
||||
/** Returns true if the state of the document differs from the state it was in when
|
||||
setSavePoint() was last called.
|
||||
|
||||
@see setSavePoint
|
||||
*/
|
||||
bool hasChangedSinceSavePoint() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Searches for a word-break. */
|
||||
Position findWordBreakAfter (const Position& position) const noexcept;
|
||||
/** Searches for a word-break. */
|
||||
Position findWordBreakBefore (const Position& position) const noexcept;
|
||||
/** Finds the token that contains the given position. */
|
||||
void findTokenContaining (const Position& pos, Position& start, Position& end) const noexcept;
|
||||
/** Finds the line that contains the given position. */
|
||||
void findLineContaining (const Position& pos, Position& start, Position& end) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** An object that receives callbacks from the CodeDocument when its text changes.
|
||||
@see CodeDocument::addListener, CodeDocument::removeListener
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
Listener() = default;
|
||||
virtual ~Listener() = default;
|
||||
|
||||
/** Called by a CodeDocument when text is added. */
|
||||
virtual void codeDocumentTextInserted (const String& newText, int insertIndex) = 0;
|
||||
|
||||
/** Called by a CodeDocument when text is deleted. */
|
||||
virtual void codeDocumentTextDeleted (int startIndex, int endIndex) = 0;
|
||||
};
|
||||
|
||||
/** Registers a listener object to receive callbacks when the document changes.
|
||||
If the listener is already registered, this method has no effect.
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (Listener* listener);
|
||||
|
||||
/** Deregisters a listener.
|
||||
@see addListener
|
||||
*/
|
||||
void removeListener (Listener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** Iterates the text in a CodeDocument.
|
||||
|
||||
This class lets you read characters from a CodeDocument. It's designed to be used
|
||||
by a CodeTokeniser object.
|
||||
|
||||
@see CodeDocument
|
||||
*/
|
||||
class JUCE_API Iterator
|
||||
{
|
||||
public:
|
||||
/** Creates an uninitialised iterator.
|
||||
Don't attempt to call any methods on this until you've given it an
|
||||
owner document to refer to!
|
||||
*/
|
||||
Iterator() noexcept;
|
||||
|
||||
Iterator (const CodeDocument& document) noexcept;
|
||||
Iterator (CodeDocument::Position) noexcept;
|
||||
~Iterator() noexcept;
|
||||
|
||||
Iterator (const Iterator&) = default;
|
||||
Iterator& operator= (const Iterator&) = default;
|
||||
|
||||
/** Reads the next character and returns it. Returns 0 if you try to
|
||||
read past the document's end.
|
||||
@see peekNextChar, previousChar
|
||||
*/
|
||||
juce_wchar nextChar() noexcept;
|
||||
|
||||
/** Reads the next character without moving the current position. */
|
||||
juce_wchar peekNextChar() const noexcept;
|
||||
|
||||
/** Reads the previous character and returns it. Returns 0 if you try to
|
||||
read past the document's start.
|
||||
@see isSOF, peekPreviousChar, nextChar
|
||||
*/
|
||||
juce_wchar previousChar() noexcept;
|
||||
|
||||
/** Reads the next character without moving the current position. */
|
||||
juce_wchar peekPreviousChar() const noexcept;
|
||||
|
||||
/** Advances the position by one character. */
|
||||
void skip() noexcept;
|
||||
|
||||
/** Returns the position as the number of characters from the start of the document. */
|
||||
int getPosition() const noexcept { return position; }
|
||||
|
||||
/** Skips over any whitespace characters until the next character is non-whitespace. */
|
||||
void skipWhitespace() noexcept;
|
||||
|
||||
/** Skips forward until the next character will be the first character on the next line */
|
||||
void skipToEndOfLine() noexcept;
|
||||
|
||||
/** Skips backward until the next character will be the first character on this line */
|
||||
void skipToStartOfLine() noexcept;
|
||||
|
||||
/** Returns the line number of the next character. */
|
||||
int getLine() const noexcept { return line; }
|
||||
|
||||
/** Returns true if the iterator has reached the end of the document. */
|
||||
bool isEOF() const noexcept;
|
||||
|
||||
/** Returns true if the iterator is at the start of the document. */
|
||||
bool isSOF() const noexcept;
|
||||
|
||||
/** Convert this iterator to a CodeDocument::Position. */
|
||||
CodeDocument::Position toPosition() const;
|
||||
|
||||
private:
|
||||
bool reinitialiseCharPtr() const;
|
||||
|
||||
const CodeDocument* document;
|
||||
mutable String::CharPointerType charPointer { nullptr };
|
||||
int line = 0, position = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct InsertAction;
|
||||
struct DeleteAction;
|
||||
friend class Iterator;
|
||||
friend class Position;
|
||||
|
||||
OwnedArray<CodeDocumentLine> lines;
|
||||
Array<Position*> positionsToMaintain;
|
||||
UndoManager undoManager;
|
||||
int currentActionIndex = 0, indexOfSavedState = -1;
|
||||
int maximumLineLength = -1;
|
||||
ListenerList<Listener> listeners;
|
||||
String newLineChars { "\r\n" };
|
||||
|
||||
void insert (const String& text, int insertPos, bool undoable);
|
||||
void remove (int startPos, int endPos, bool undoable);
|
||||
void checkLastLineStatus();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CodeDocument)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,486 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class CodeTokeniser;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A text editor component designed specifically for source code.
|
||||
|
||||
This is designed to handle syntax highlighting and fast editing of very large
|
||||
files.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API CodeEditorComponent : public TextInputTarget,
|
||||
public Component,
|
||||
public ApplicationCommandTarget
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an editor for a document.
|
||||
|
||||
The tokeniser object is optional - pass nullptr to disable syntax highlighting.
|
||||
The object that you pass in is not owned or deleted by the editor - you must
|
||||
make sure that it doesn't get deleted while this component is still using it.
|
||||
|
||||
@see CodeDocument
|
||||
*/
|
||||
CodeEditorComponent (CodeDocument& document,
|
||||
CodeTokeniser* codeTokeniser);
|
||||
|
||||
/** Destructor. */
|
||||
~CodeEditorComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the code document that this component is editing. */
|
||||
CodeDocument& getDocument() const noexcept { return document; }
|
||||
|
||||
/** Loads the given content into the document.
|
||||
This will completely reset the CodeDocument object, clear its undo history,
|
||||
and fill it with this text.
|
||||
*/
|
||||
void loadContent (const String& newContent);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the standard character width. */
|
||||
float getCharWidth() const noexcept { return charWidth; }
|
||||
|
||||
/** Returns the height of a line of text, in pixels. */
|
||||
int getLineHeight() const noexcept { return lineHeight; }
|
||||
|
||||
/** Returns the number of whole lines visible on the screen,
|
||||
This doesn't include a cut-off line that might be visible at the bottom if the
|
||||
component's height isn't an exact multiple of the line-height.
|
||||
*/
|
||||
int getNumLinesOnScreen() const noexcept { return linesOnScreen; }
|
||||
|
||||
/** Returns the index of the first line that's visible at the top of the editor. */
|
||||
int getFirstLineOnScreen() const noexcept { return firstLineOnScreen; }
|
||||
|
||||
/** Returns the number of whole columns visible on the screen.
|
||||
This doesn't include any cut-off columns at the right-hand edge.
|
||||
*/
|
||||
int getNumColumnsOnScreen() const noexcept { return columnsOnScreen; }
|
||||
|
||||
/** Returns the current caret position. */
|
||||
CodeDocument::Position getCaretPos() const { return caretPos; }
|
||||
|
||||
/** Returns the total number of codepoints in the string. */
|
||||
int getTotalNumChars() const override { return document.getNumCharacters(); }
|
||||
|
||||
/** Moves the caret.
|
||||
If selecting is true, the section of the document between the current
|
||||
caret position and the new one will become selected. If false, any currently
|
||||
selected region will be deselected.
|
||||
*/
|
||||
void moveCaretTo (const CodeDocument::Position& newPos, bool selecting);
|
||||
|
||||
/** Returns the on-screen position of a character in the document.
|
||||
The rectangle returned is relative to this component's top-left origin.
|
||||
*/
|
||||
Rectangle<int> getCharacterBounds (const CodeDocument::Position& pos) const;
|
||||
|
||||
/** Finds the character at a given on-screen position.
|
||||
The coordinates are relative to this component's top-left origin.
|
||||
*/
|
||||
CodeDocument::Position getPositionAt (int x, int y) const;
|
||||
|
||||
/** Returns the start of the selection as a position. */
|
||||
CodeDocument::Position getSelectionStart() const { return selectionStart; }
|
||||
|
||||
/** Returns the end of the selection as a position. */
|
||||
CodeDocument::Position getSelectionEnd() const { return selectionEnd; }
|
||||
|
||||
/** Enables or disables the line-number display in the gutter. */
|
||||
void setLineNumbersShown (bool shouldBeShown);
|
||||
|
||||
/** Returns the number of characters from the beginning of the document to the caret. */
|
||||
int getCaretPosition() const override { return getCaretPos().getPosition(); }
|
||||
|
||||
/** @see getPositionAt */
|
||||
int getCharIndexForPoint (Point<int> point) const override;
|
||||
|
||||
/** Returns the bounds of the caret at a particular location in the text. */
|
||||
Rectangle<int> getCaretRectangleForCharIndex (int index) const override
|
||||
{
|
||||
return getCharacterBounds ({ document, index });
|
||||
}
|
||||
|
||||
/** Returns the bounding box for a range of text in the editor. As the range may span
|
||||
multiple lines, this method returns a RectangleList.
|
||||
|
||||
The bounds are relative to the component's top-left and may extend beyond the bounds
|
||||
of the component if the text is long and word wrapping is disabled.
|
||||
*/
|
||||
RectangleList<int> getTextBounds (Range<int> textRange) const override;
|
||||
|
||||
//==============================================================================
|
||||
bool moveCaretLeft (bool moveInWholeWordSteps, bool selecting);
|
||||
bool moveCaretRight (bool moveInWholeWordSteps, bool selecting);
|
||||
bool moveCaretUp (bool selecting);
|
||||
bool moveCaretDown (bool selecting);
|
||||
bool scrollDown();
|
||||
bool scrollUp();
|
||||
bool pageUp (bool selecting);
|
||||
bool pageDown (bool selecting);
|
||||
bool moveCaretToTop (bool selecting);
|
||||
bool moveCaretToStartOfLine (bool selecting);
|
||||
bool moveCaretToEnd (bool selecting);
|
||||
bool moveCaretToEndOfLine (bool selecting);
|
||||
bool deleteBackwards (bool moveInWholeWordSteps);
|
||||
bool deleteForwards (bool moveInWholeWordSteps);
|
||||
bool deleteWhitespaceBackwardsToTabStop();
|
||||
virtual bool copyToClipboard();
|
||||
virtual bool cutToClipboard();
|
||||
virtual bool pasteFromClipboard();
|
||||
bool undo();
|
||||
bool redo();
|
||||
|
||||
void selectRegion (const CodeDocument::Position& start, const CodeDocument::Position& end);
|
||||
bool selectAll();
|
||||
void deselectAll();
|
||||
|
||||
void scrollToLine (int newFirstLineOnScreen);
|
||||
void scrollBy (int deltaLines);
|
||||
void scrollToColumn (int newFirstColumnOnScreen);
|
||||
void scrollToKeepCaretOnScreen();
|
||||
void scrollToKeepLinesOnScreen (Range<int> linesToShow);
|
||||
|
||||
void insertTextAtCaret (const String& textToInsert) override;
|
||||
void insertTabAtCaret();
|
||||
|
||||
void indentSelection();
|
||||
void unindentSelection();
|
||||
|
||||
//==============================================================================
|
||||
Range<int> getHighlightedRegion() const override;
|
||||
bool isHighlightActive() const noexcept;
|
||||
void setHighlightedRegion (const Range<int>& newRange) override;
|
||||
String getTextInRange (const Range<int>& range) const override;
|
||||
|
||||
//==============================================================================
|
||||
/** Can be used to save and restore the editor's caret position, selection state, etc. */
|
||||
struct State
|
||||
{
|
||||
/** Creates an object containing the state of the given editor. */
|
||||
State (const CodeEditorComponent&);
|
||||
/** Creates a state object from a string that was previously created with toString(). */
|
||||
State (const String& stringifiedVersion);
|
||||
State (const State&) noexcept;
|
||||
|
||||
/** Updates the given editor with this saved state. */
|
||||
void restoreState (CodeEditorComponent&) const;
|
||||
|
||||
/** Returns a stringified version of this state that can be used to recreate it later. */
|
||||
String toString() const;
|
||||
|
||||
private:
|
||||
int lastTopLine, lastCaretPos, lastSelectionEnd;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the current tab settings.
|
||||
This lets you change the tab size and whether pressing the tab key inserts a
|
||||
tab character, or its equivalent number of spaces.
|
||||
*/
|
||||
void setTabSize (int numSpacesPerTab, bool insertSpacesInsteadOfTabCharacters);
|
||||
|
||||
/** Returns the current number of spaces per tab.
|
||||
@see setTabSize
|
||||
*/
|
||||
int getTabSize() const noexcept { return spacesPerTab; }
|
||||
|
||||
/** Returns true if the tab key will insert spaces instead of actual tab characters.
|
||||
@see setTabSize
|
||||
*/
|
||||
bool areSpacesInsertedForTabs() const { return useSpacesForTabs; }
|
||||
|
||||
/** Returns a string containing spaces or tab characters to generate the given number of spaces. */
|
||||
String getTabString (int numSpaces) const;
|
||||
|
||||
/** Changes the font.
|
||||
Make sure you only use a fixed-width font, or this component will look pretty nasty!
|
||||
*/
|
||||
void setFont (const Font& newFont);
|
||||
|
||||
/** Returns the font that the editor is using. */
|
||||
const Font& getFont() const noexcept { return font; }
|
||||
|
||||
/** Makes the editor read-only. */
|
||||
void setReadOnly (bool shouldBeReadOnly) noexcept;
|
||||
|
||||
/** Returns true if the editor is set to be read-only. */
|
||||
bool isReadOnly() const noexcept { return readOnly; }
|
||||
|
||||
//==============================================================================
|
||||
/** Defines a syntax highlighting colour scheme */
|
||||
struct JUCE_API ColourScheme
|
||||
{
|
||||
/** Defines a colour for a token type */
|
||||
struct TokenType
|
||||
{
|
||||
String name;
|
||||
Colour colour;
|
||||
};
|
||||
|
||||
Array<TokenType> types;
|
||||
|
||||
void set (const String& name, Colour colour);
|
||||
};
|
||||
|
||||
/** Changes the syntax highlighting scheme.
|
||||
The token type values are dependent on the tokeniser being used - use
|
||||
CodeTokeniser::getTokenTypes() to get a list of the token types.
|
||||
@see getColourForTokenType
|
||||
*/
|
||||
void setColourScheme (const ColourScheme& scheme);
|
||||
|
||||
/** Returns the current syntax highlighting colour scheme. */
|
||||
const ColourScheme& getColourScheme() const noexcept { return colourScheme; }
|
||||
|
||||
/** Returns one the syntax highlighting colour for the given token.
|
||||
The token type values are dependent on the tokeniser being used.
|
||||
@see setColourScheme
|
||||
*/
|
||||
Colour getColourForTokenType (int tokenType) const;
|
||||
|
||||
/** Rebuilds the syntax highlighting for a section of text.
|
||||
|
||||
This happens automatically any time the CodeDocument is edited, but this
|
||||
method lets you change text colours even when the CodeDocument hasn't changed.
|
||||
|
||||
For example, you could use this to highlight tokens as the cursor moves.
|
||||
To do so you'll need to tell your custom CodeTokeniser where the token you
|
||||
want to highlight is, and make it return a special type of token. Then you
|
||||
should call this method supplying the range of the highlighted text.
|
||||
@see CodeTokeniser
|
||||
*/
|
||||
void retokenise (int startIndex, int endIndex);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the editor.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1004500, /**< A colour to use to fill the editor's background. */
|
||||
highlightColourId = 0x1004502, /**< The colour to use for the highlighted background under selected text. */
|
||||
defaultTextColourId = 0x1004503, /**< The colour to use for text when no syntax colouring is enabled. */
|
||||
lineNumberBackgroundId = 0x1004504, /**< The colour to use for filling the background of the line-number gutter. */
|
||||
lineNumberTextId = 0x1004505, /**< The colour to use for drawing the line numbers. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the size of the scrollbars. */
|
||||
void setScrollbarThickness (int thickness);
|
||||
|
||||
/** Returns the thickness of the scrollbars. */
|
||||
int getScrollbarThickness() const noexcept { return scrollbarThickness; }
|
||||
|
||||
//==============================================================================
|
||||
/** Called when the return key is pressed - this can be overridden for custom behaviour. */
|
||||
virtual void handleReturnKey();
|
||||
/** Called when the tab key is pressed - this can be overridden for custom behaviour. */
|
||||
virtual void handleTabKey();
|
||||
/** Called when the escape key is pressed - this can be overridden for custom behaviour. */
|
||||
virtual void handleEscapeKey();
|
||||
|
||||
/** Called when the view position is scrolled horizontally or vertically. */
|
||||
virtual void editorViewportPositionChanged();
|
||||
|
||||
/** Called when the caret position moves. */
|
||||
virtual void caretPositionMoved();
|
||||
|
||||
//==============================================================================
|
||||
/** This adds the items to the popup menu.
|
||||
|
||||
By default it adds the cut/copy/paste items, but you can override this if
|
||||
you need to replace these with your own items.
|
||||
|
||||
If you want to add your own items to the existing ones, you can override this,
|
||||
call the base class's addPopupMenuItems() method, then append your own items.
|
||||
|
||||
When the menu has been shown, performPopupMenuAction() will be called to
|
||||
perform the item that the user has chosen.
|
||||
|
||||
If this was triggered by a mouse-click, the mouseClickEvent parameter will be
|
||||
a pointer to the info about it, or may be null if the menu is being triggered
|
||||
by some other means.
|
||||
|
||||
@see performPopupMenuAction, setPopupMenuEnabled, isPopupMenuEnabled
|
||||
*/
|
||||
virtual void addPopupMenuItems (PopupMenu& menuToAddTo,
|
||||
const MouseEvent* mouseClickEvent);
|
||||
|
||||
/** This is called to perform one of the items that was shown on the popup menu.
|
||||
|
||||
If you've overridden addPopupMenuItems(), you should also override this
|
||||
to perform the actions that you've added.
|
||||
|
||||
If you've overridden addPopupMenuItems() but have still left the default items
|
||||
on the menu, remember to call the superclass's performPopupMenuAction()
|
||||
so that it can perform the default actions if that's what the user clicked on.
|
||||
|
||||
@see addPopupMenuItems, setPopupMenuEnabled, isPopupMenuEnabled
|
||||
*/
|
||||
virtual void performPopupMenuAction (int menuItemID);
|
||||
|
||||
/** Specifies a command-manager which the editor will notify whenever the state
|
||||
of any of its commands changes.
|
||||
If you're making use of the editor's ApplicationCommandTarget interface, then
|
||||
you should also use this to tell it which command manager it should use. Make
|
||||
sure that the manager does not go out of scope while the editor is using it. You
|
||||
can pass a nullptr here to disable this.
|
||||
*/
|
||||
void setCommandManager (ApplicationCommandManager* newManager) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDoubleClick (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
|
||||
/** @internal */
|
||||
void focusGained (FocusChangeType) override;
|
||||
/** @internal */
|
||||
void focusLost (FocusChangeType) override;
|
||||
/** @internal */
|
||||
bool isTextInputActive() const override;
|
||||
/** @internal */
|
||||
void setTemporaryUnderlining (const Array<Range<int>>&) override;
|
||||
/** @internal */
|
||||
ApplicationCommandTarget* getNextCommandTarget() override;
|
||||
/** @internal */
|
||||
void getAllCommands (Array<CommandID>&) override;
|
||||
/** @internal */
|
||||
void getCommandInfo (CommandID, ApplicationCommandInfo&) override;
|
||||
/** @internal */
|
||||
bool perform (const InvocationInfo&) override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
CodeDocument& document;
|
||||
|
||||
Font font { withDefaultMetrics (FontOptions{}) };
|
||||
int firstLineOnScreen = 0, spacesPerTab = 4;
|
||||
float charWidth = 0;
|
||||
int lineHeight = 0, linesOnScreen = 0, columnsOnScreen = 0;
|
||||
int scrollbarThickness = 16, columnToTryToMaintain = -1;
|
||||
bool readOnly = false, useSpacesForTabs = true, showLineNumbers = false, shouldFollowDocumentChanges = false;
|
||||
double xOffset = 0;
|
||||
CodeDocument::Position caretPos, selectionStart, selectionEnd;
|
||||
|
||||
std::unique_ptr<CaretComponent> caret;
|
||||
ScrollBar verticalScrollBar { true }, horizontalScrollBar { false };
|
||||
ApplicationCommandManager* appCommandManager = nullptr;
|
||||
|
||||
class Pimpl;
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
|
||||
class GutterComponent;
|
||||
std::unique_ptr<GutterComponent> gutter;
|
||||
|
||||
class CodeEditorAccessibilityHandler;
|
||||
|
||||
enum DragType
|
||||
{
|
||||
notDragging,
|
||||
draggingSelectionStart,
|
||||
draggingSelectionEnd
|
||||
};
|
||||
|
||||
DragType dragType = notDragging;
|
||||
|
||||
//==============================================================================
|
||||
CodeTokeniser* codeTokeniser;
|
||||
ColourScheme colourScheme;
|
||||
|
||||
class CodeEditorLine;
|
||||
OwnedArray<CodeEditorLine> lines;
|
||||
void rebuildLineTokens();
|
||||
void rebuildLineTokensAsync();
|
||||
void codeDocumentChanged (int start, int end);
|
||||
|
||||
Array<CodeDocument::Iterator> cachedIterators;
|
||||
void clearCachedIterators (int firstLineToBeInvalid);
|
||||
void updateCachedIterators (int maxLineNum);
|
||||
void getIteratorForPosition (int position, CodeDocument::Iterator&);
|
||||
|
||||
void moveLineDelta (int delta, bool selecting);
|
||||
int getGutterSize() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
void insertText (const String&);
|
||||
virtual void updateCaretPosition();
|
||||
void updateScrollBars();
|
||||
void scrollToLineInternal (int line);
|
||||
void scrollToColumnInternal (double column);
|
||||
void newTransaction();
|
||||
void cut();
|
||||
void indentSelectedLines (int spacesToAdd);
|
||||
bool skipBackwardsToPreviousTab();
|
||||
bool performCommand (CommandID);
|
||||
void setSelection (CodeDocument::Position, CodeDocument::Position);
|
||||
|
||||
int indexToColumn (int line, int index) const noexcept;
|
||||
int columnToIndex (int line, int column) const noexcept;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CodeEditorComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A base class for tokenising code so that the syntax can be displayed in a
|
||||
code editor.
|
||||
|
||||
@see CodeDocument, CodeEditorComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API CodeTokeniser
|
||||
{
|
||||
public:
|
||||
CodeTokeniser() = default;
|
||||
virtual ~CodeTokeniser() = default;
|
||||
|
||||
//==============================================================================
|
||||
/** Reads the next token from the source and returns its token type.
|
||||
|
||||
This must leave the source pointing to the first character in the
|
||||
next token.
|
||||
*/
|
||||
virtual int readNextToken (CodeDocument::Iterator& source) = 0;
|
||||
|
||||
/** Returns a suggested syntax highlighting colour scheme. */
|
||||
virtual CodeEditorComponent::ColourScheme getDefaultColourScheme() = 0;
|
||||
|
||||
private:
|
||||
JUCE_LEAK_DETECTOR (CodeTokeniser)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct LuaTokeniserFunctions
|
||||
{
|
||||
static bool isReservedKeyword (String::CharPointerType token, const int tokenLength) noexcept
|
||||
{
|
||||
static const char* const keywords2Char[] =
|
||||
{ "if", "or", "in", "do", nullptr };
|
||||
|
||||
static const char* const keywords3Char[] =
|
||||
{ "and", "end", "for", "nil", "not", nullptr };
|
||||
|
||||
static const char* const keywords4Char[] =
|
||||
{ "then", "true", "else", nullptr };
|
||||
|
||||
static const char* const keywords5Char[] =
|
||||
{ "false", "local", "until", "while", "break", nullptr };
|
||||
|
||||
static const char* const keywords6Char[] =
|
||||
{ "repeat", "return", "elseif", nullptr};
|
||||
|
||||
static const char* const keywordsOther[] =
|
||||
{ "function", "@interface", "@end", "@synthesize", "@dynamic", "@public",
|
||||
"@private", "@property", "@protected", "@class", nullptr };
|
||||
|
||||
const char* const* k;
|
||||
|
||||
switch (tokenLength)
|
||||
{
|
||||
case 2: k = keywords2Char; break;
|
||||
case 3: k = keywords3Char; break;
|
||||
case 4: k = keywords4Char; break;
|
||||
case 5: k = keywords5Char; break;
|
||||
case 6: k = keywords6Char; break;
|
||||
|
||||
default:
|
||||
if (tokenLength < 2 || tokenLength > 16)
|
||||
return false;
|
||||
|
||||
k = keywordsOther;
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; k[i] != nullptr; ++i)
|
||||
if (token.compare (CharPointer_ASCII (k[i])) == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static int parseIdentifier (Iterator& source) noexcept
|
||||
{
|
||||
int tokenLength = 0;
|
||||
String::CharPointerType::CharType possibleIdentifier[100] = {};
|
||||
String::CharPointerType possible (possibleIdentifier);
|
||||
|
||||
while (CppTokeniserFunctions::isIdentifierBody (source.peekNextChar()))
|
||||
{
|
||||
auto c = source.nextChar();
|
||||
|
||||
if (tokenLength < 20)
|
||||
possible.write (c);
|
||||
|
||||
++tokenLength;
|
||||
}
|
||||
|
||||
if (tokenLength > 1 && tokenLength <= 16)
|
||||
{
|
||||
possible.writeNull();
|
||||
|
||||
if (isReservedKeyword (String::CharPointerType (possibleIdentifier), tokenLength))
|
||||
return LuaTokeniser::tokenType_keyword;
|
||||
}
|
||||
|
||||
return LuaTokeniser::tokenType_identifier;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static int readNextToken (Iterator& source)
|
||||
{
|
||||
source.skipWhitespace();
|
||||
|
||||
auto firstChar = source.peekNextChar();
|
||||
|
||||
switch (firstChar)
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
case '.':
|
||||
{
|
||||
auto result = CppTokeniserFunctions::parseNumber (source);
|
||||
|
||||
if (result == LuaTokeniser::tokenType_error)
|
||||
{
|
||||
source.skip();
|
||||
|
||||
if (firstChar == '.')
|
||||
return LuaTokeniser::tokenType_punctuation;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case ',':
|
||||
case ';':
|
||||
case ':':
|
||||
source.skip();
|
||||
return LuaTokeniser::tokenType_punctuation;
|
||||
|
||||
case '(': case ')':
|
||||
case '{': case '}':
|
||||
case '[': case ']':
|
||||
source.skip();
|
||||
return LuaTokeniser::tokenType_bracket;
|
||||
|
||||
case '"':
|
||||
case '\'':
|
||||
CppTokeniserFunctions::skipQuotedString (source);
|
||||
return LuaTokeniser::tokenType_string;
|
||||
|
||||
case '+':
|
||||
source.skip();
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '+', '=');
|
||||
return LuaTokeniser::tokenType_operator;
|
||||
|
||||
case '-':
|
||||
{
|
||||
source.skip();
|
||||
auto result = CppTokeniserFunctions::parseNumber (source);
|
||||
|
||||
if (source.peekNextChar() == '-')
|
||||
{
|
||||
source.skipToEndOfLine();
|
||||
return LuaTokeniser::tokenType_comment;
|
||||
}
|
||||
|
||||
if (result == LuaTokeniser::tokenType_error)
|
||||
{
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '-', '=');
|
||||
return LuaTokeniser::tokenType_operator;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case '*': case '%':
|
||||
case '=': case '!':
|
||||
source.skip();
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '=');
|
||||
return LuaTokeniser::tokenType_operator;
|
||||
|
||||
case '?':
|
||||
case '~':
|
||||
source.skip();
|
||||
return LuaTokeniser::tokenType_operator;
|
||||
|
||||
case '<': case '>':
|
||||
case '|': case '&': case '^':
|
||||
source.skip();
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, firstChar);
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '=');
|
||||
return LuaTokeniser::tokenType_operator;
|
||||
|
||||
default:
|
||||
if (CppTokeniserFunctions::isIdentifierStart (firstChar))
|
||||
return parseIdentifier (source);
|
||||
|
||||
source.skip();
|
||||
break;
|
||||
}
|
||||
|
||||
return LuaTokeniser::tokenType_error;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
LuaTokeniser::LuaTokeniser() {}
|
||||
LuaTokeniser::~LuaTokeniser() {}
|
||||
|
||||
int LuaTokeniser::readNextToken (CodeDocument::Iterator& source)
|
||||
{
|
||||
return LuaTokeniserFunctions::readNextToken (source);
|
||||
}
|
||||
|
||||
CodeEditorComponent::ColourScheme LuaTokeniser::getDefaultColourScheme()
|
||||
{
|
||||
static const CodeEditorComponent::ColourScheme::TokenType types[] =
|
||||
{
|
||||
{ "Error", Colour (0xffcc0000) },
|
||||
{ "Comment", Colour (0xff3c3c3c) },
|
||||
{ "Keyword", Colour (0xff0000cc) },
|
||||
{ "Operator", Colour (0xff225500) },
|
||||
{ "Identifier", Colour (0xff000000) },
|
||||
{ "Integer", Colour (0xff880000) },
|
||||
{ "Float", Colour (0xff885500) },
|
||||
{ "String", Colour (0xff990099) },
|
||||
{ "Bracket", Colour (0xff000055) },
|
||||
{ "Punctuation", Colour (0xff004400) }
|
||||
};
|
||||
|
||||
CodeEditorComponent::ColourScheme cs;
|
||||
|
||||
for (auto& t : types)
|
||||
cs.set (t.name, Colour (t.colour));
|
||||
|
||||
return cs;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API LuaTokeniser : public CodeTokeniser
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
LuaTokeniser();
|
||||
~LuaTokeniser() override;
|
||||
|
||||
//==============================================================================
|
||||
int readNextToken (CodeDocument::Iterator&) override;
|
||||
CodeEditorComponent::ColourScheme getDefaultColourScheme() override;
|
||||
|
||||
/** The token values returned by this tokeniser. */
|
||||
enum TokenType
|
||||
{
|
||||
tokenType_error = 0,
|
||||
tokenType_comment,
|
||||
tokenType_keyword,
|
||||
tokenType_operator,
|
||||
tokenType_identifier,
|
||||
tokenType_integer,
|
||||
tokenType_float,
|
||||
tokenType_string,
|
||||
tokenType_bracket,
|
||||
tokenType_punctuation
|
||||
};
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LuaTokeniser)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
XmlTokeniser::XmlTokeniser() {}
|
||||
XmlTokeniser::~XmlTokeniser() {}
|
||||
|
||||
CodeEditorComponent::ColourScheme XmlTokeniser::getDefaultColourScheme()
|
||||
{
|
||||
struct Type
|
||||
{
|
||||
const char* name;
|
||||
uint32 colour;
|
||||
};
|
||||
|
||||
const Type types[] =
|
||||
{
|
||||
{ "Error", 0xffcc0000 },
|
||||
{ "Comment", 0xff00aa00 },
|
||||
{ "Keyword", 0xff0000cc },
|
||||
{ "Operator", 0xff225500 },
|
||||
{ "Identifier", 0xff000000 },
|
||||
{ "String", 0xff990099 },
|
||||
{ "Bracket", 0xff000055 },
|
||||
{ "Punctuation", 0xff004400 },
|
||||
{ "Preprocessor Text", 0xff660000 }
|
||||
};
|
||||
|
||||
CodeEditorComponent::ColourScheme cs;
|
||||
|
||||
for (auto& t : types)
|
||||
cs.set (t.name, Colour (t.colour));
|
||||
|
||||
return cs;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static void skipToEndOfXmlDTD (Iterator& source) noexcept
|
||||
{
|
||||
bool lastWasQuestionMark = false;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto c = source.nextChar();
|
||||
|
||||
if (c == 0 || (c == '>' && lastWasQuestionMark))
|
||||
break;
|
||||
|
||||
lastWasQuestionMark = (c == '?');
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static void skipToEndOfXmlComment (Iterator& source) noexcept
|
||||
{
|
||||
juce_wchar last[2] = {};
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto c = source.nextChar();
|
||||
|
||||
if (c == 0 || (c == '>' && last[0] == '-' && last[1] == '-'))
|
||||
break;
|
||||
|
||||
last[1] = last[0];
|
||||
last[0] = c;
|
||||
}
|
||||
}
|
||||
|
||||
int XmlTokeniser::readNextToken (CodeDocument::Iterator& source)
|
||||
{
|
||||
source.skipWhitespace();
|
||||
auto firstChar = source.peekNextChar();
|
||||
|
||||
switch (firstChar)
|
||||
{
|
||||
case 0: break;
|
||||
|
||||
case '"':
|
||||
case '\'':
|
||||
CppTokeniserFunctions::skipQuotedString (source);
|
||||
return tokenType_string;
|
||||
|
||||
case '<':
|
||||
{
|
||||
source.skip();
|
||||
source.skipWhitespace();
|
||||
auto nextChar = source.peekNextChar();
|
||||
|
||||
if (nextChar == '?')
|
||||
{
|
||||
source.skip();
|
||||
skipToEndOfXmlDTD (source);
|
||||
return tokenType_preprocessor;
|
||||
}
|
||||
|
||||
if (nextChar == '!')
|
||||
{
|
||||
source.skip();
|
||||
|
||||
if (source.peekNextChar() == '-')
|
||||
{
|
||||
source.skip();
|
||||
|
||||
if (source.peekNextChar() == '-')
|
||||
{
|
||||
skipToEndOfXmlComment (source);
|
||||
return tokenType_comment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '/');
|
||||
CppTokeniserFunctions::parseIdentifier (source);
|
||||
source.skipWhitespace();
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '/');
|
||||
source.skipWhitespace();
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '>');
|
||||
return tokenType_keyword;
|
||||
}
|
||||
|
||||
case '>':
|
||||
source.skip();
|
||||
return tokenType_keyword;
|
||||
|
||||
case '/':
|
||||
source.skip();
|
||||
source.skipWhitespace();
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '>');
|
||||
return tokenType_keyword;
|
||||
|
||||
case '=':
|
||||
case ':':
|
||||
source.skip();
|
||||
return tokenType_operator;
|
||||
|
||||
default:
|
||||
if (CppTokeniserFunctions::isIdentifierStart (firstChar))
|
||||
CppTokeniserFunctions::parseIdentifier (source);
|
||||
|
||||
source.skip();
|
||||
break;
|
||||
};
|
||||
|
||||
return tokenType_identifier;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API XmlTokeniser : public CodeTokeniser
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
XmlTokeniser();
|
||||
~XmlTokeniser() override;
|
||||
|
||||
//==============================================================================
|
||||
int readNextToken (CodeDocument::Iterator&) override;
|
||||
CodeEditorComponent::ColourScheme getDefaultColourScheme() override;
|
||||
|
||||
/** The token values returned by this tokeniser. */
|
||||
enum TokenType
|
||||
{
|
||||
tokenType_error = 0,
|
||||
tokenType_comment,
|
||||
tokenType_keyword,
|
||||
tokenType_operator,
|
||||
tokenType_identifier,
|
||||
tokenType_string,
|
||||
tokenType_bracket,
|
||||
tokenType_punctuation,
|
||||
tokenType_preprocessor
|
||||
};
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlTokeniser)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::detail
|
||||
{
|
||||
|
||||
#if JUCE_WEB_BROWSER || DOXYGEN
|
||||
|
||||
struct WebSliderRelayEvents
|
||||
{
|
||||
WebSliderRelayEvents() = delete;
|
||||
|
||||
struct Event
|
||||
{
|
||||
String eventType;
|
||||
DynamicObject::Ptr object;
|
||||
|
||||
static std::optional<Event> extract (const var& v)
|
||||
{
|
||||
auto* dynObj = v.getDynamicObject();
|
||||
|
||||
if (dynObj == nullptr)
|
||||
return std::nullopt;
|
||||
|
||||
const auto eventTypeProp = dynObj->getProperty (eventTypeKey);
|
||||
|
||||
if (! eventTypeProp.isString())
|
||||
return std::nullopt;
|
||||
|
||||
return Event { eventTypeProp.toString(), dynObj };
|
||||
}
|
||||
|
||||
static inline const Identifier eventTypeKey { "eventType" };
|
||||
};
|
||||
|
||||
struct ValueChanged
|
||||
{
|
||||
float newValue;
|
||||
|
||||
static std::optional<ValueChanged> extract (const Event& event)
|
||||
{
|
||||
if (event.eventType != eventId.toString())
|
||||
return std::nullopt;
|
||||
|
||||
const auto newValue = event.object->getProperty (newValueKey);
|
||||
|
||||
if (! (newValue.isInt() || newValue.isInt64() || newValue.isDouble()))
|
||||
return std::nullopt;
|
||||
|
||||
return ValueChanged { (float) newValue };
|
||||
}
|
||||
|
||||
static inline const Identifier eventId { "valueChanged" };
|
||||
static inline const Identifier newValueKey { "value" };
|
||||
};
|
||||
|
||||
struct SliderDragStarted
|
||||
{
|
||||
static std::optional<SliderDragStarted> extract (const Event& event)
|
||||
{
|
||||
if (event.eventType != eventId.toString())
|
||||
return std::nullopt;
|
||||
|
||||
return SliderDragStarted{};
|
||||
}
|
||||
|
||||
static inline const Identifier eventId { "sliderDragStarted" };
|
||||
};
|
||||
|
||||
struct SliderDragEnded
|
||||
{
|
||||
static std::optional<SliderDragEnded> extract (const Event& event)
|
||||
{
|
||||
if (event.eventType != eventId.toString())
|
||||
return std::nullopt;
|
||||
|
||||
return SliderDragEnded{};
|
||||
}
|
||||
|
||||
static inline const Identifier eventId { "sliderDragEnded" };
|
||||
};
|
||||
|
||||
struct InitialUpdateRequested
|
||||
{
|
||||
static std::optional<InitialUpdateRequested> extract (const Event& event)
|
||||
{
|
||||
if (event.eventType != eventId.toString())
|
||||
return std::nullopt;
|
||||
|
||||
return InitialUpdateRequested{};
|
||||
}
|
||||
|
||||
static inline const Identifier eventId { "requestInitialUpdate" };
|
||||
};
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct WebToggleButtonRelayEvents
|
||||
{
|
||||
WebToggleButtonRelayEvents() = delete;
|
||||
|
||||
struct Event
|
||||
{
|
||||
String eventType;
|
||||
DynamicObject::Ptr object;
|
||||
|
||||
static std::optional<Event> extract (const var& v)
|
||||
{
|
||||
auto* dynObj = v.getDynamicObject();
|
||||
|
||||
if (dynObj == nullptr)
|
||||
return std::nullopt;
|
||||
|
||||
const auto eventTypeProp = dynObj->getProperty (eventTypeKey);
|
||||
|
||||
if (! eventTypeProp.isString())
|
||||
return std::nullopt;
|
||||
|
||||
return Event { eventTypeProp.toString(), dynObj };
|
||||
}
|
||||
|
||||
static inline const Identifier eventTypeKey { "eventType" };
|
||||
};
|
||||
|
||||
struct ToggleStateChanged
|
||||
{
|
||||
bool value;
|
||||
|
||||
static std::optional<ToggleStateChanged> extract (const Event& event)
|
||||
{
|
||||
if (event.eventType != eventId.toString())
|
||||
return std::nullopt;
|
||||
|
||||
const auto newState = event.object->getProperty (valueKey);
|
||||
|
||||
if (! newState.isBool())
|
||||
return std::nullopt;
|
||||
|
||||
return ToggleStateChanged { newState };
|
||||
}
|
||||
|
||||
static inline const Identifier eventId { "valueChanged" };
|
||||
static inline const Identifier valueKey { "value" };
|
||||
};
|
||||
|
||||
struct InitialUpdateRequested
|
||||
{
|
||||
static std::optional<InitialUpdateRequested> extract (const Event& event)
|
||||
{
|
||||
if (event.eventType != eventId.toString())
|
||||
return std::nullopt;
|
||||
|
||||
return InitialUpdateRequested{};
|
||||
}
|
||||
|
||||
static inline const Identifier eventId { "requestInitialUpdate" };
|
||||
};
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct WebComboBoxRelayEvents
|
||||
{
|
||||
WebComboBoxRelayEvents() = delete;
|
||||
|
||||
struct Event
|
||||
{
|
||||
String eventType;
|
||||
DynamicObject::Ptr object;
|
||||
|
||||
static std::optional<Event> extract (const var& v)
|
||||
{
|
||||
auto* dynObj = v.getDynamicObject();
|
||||
|
||||
if (dynObj == nullptr)
|
||||
return std::nullopt;
|
||||
|
||||
const auto eventTypeProp = dynObj->getProperty (eventTypeKey);
|
||||
|
||||
if (! eventTypeProp.isString())
|
||||
return std::nullopt;
|
||||
|
||||
return Event { eventTypeProp.toString(), dynObj };
|
||||
}
|
||||
|
||||
static inline const Identifier eventTypeKey { "eventType" };
|
||||
};
|
||||
|
||||
struct ValueChanged
|
||||
{
|
||||
float value;
|
||||
|
||||
static std::optional<ValueChanged> extract (const Event& event)
|
||||
{
|
||||
if (event.eventType != eventId.toString())
|
||||
return std::nullopt;
|
||||
|
||||
const auto newValue = event.object->getProperty (valueKey);
|
||||
|
||||
if (! (newValue.isInt() || newValue.isInt64() || newValue.isDouble()))
|
||||
return std::nullopt;
|
||||
|
||||
return ValueChanged { (float) newValue };
|
||||
}
|
||||
|
||||
static inline const Identifier eventId { "valueChanged" };
|
||||
static inline const Identifier valueKey { "value" };
|
||||
};
|
||||
|
||||
struct InitialUpdateRequested
|
||||
{
|
||||
static std::optional<InitialUpdateRequested> extract (const Event& event)
|
||||
{
|
||||
if (event.eventType != eventId.toString())
|
||||
return std::nullopt;
|
||||
|
||||
return InitialUpdateRequested{};
|
||||
}
|
||||
|
||||
static inline const Identifier eventId { "requestInitialUpdate" };
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,440 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class to take care of the logic involved with the loading/saving of some kind
|
||||
of document.
|
||||
|
||||
There's quite a lot of tedious logic involved in writing all the load/save/save-as
|
||||
functions you need for documents that get saved to a file, so this class attempts
|
||||
to abstract most of the boring stuff.
|
||||
|
||||
Your subclass should just implement all the pure virtual methods, and you can
|
||||
then use the higher-level public methods to do the load/save dialogs, to warn the user
|
||||
about overwriting files, etc.
|
||||
|
||||
The document object keeps track of whether it has changed since it was last saved or
|
||||
loaded, so when you change something, call its changed() method. This will set a
|
||||
flag so it knows it needs saving, and will also broadcast a change message using the
|
||||
ChangeBroadcaster base class.
|
||||
|
||||
@see ChangeBroadcaster
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileBasedDocument : public ChangeBroadcaster
|
||||
{
|
||||
public:
|
||||
/** Creates a FileBasedDocument.
|
||||
|
||||
@param fileExtension the extension to use when loading/saving files, e.g. ".doc"
|
||||
@param fileWildCard the wildcard to use in file dialogs, e.g. "*.doc"
|
||||
@param openFileDialogTitle the title to show on an open-file dialog, e.g. "Choose a file to open.."
|
||||
@param saveFileDialogTitle the title to show on an save-file dialog, e.g. "Choose a file to save as.."
|
||||
*/
|
||||
FileBasedDocument (const String& fileExtension,
|
||||
const String& fileWildCard,
|
||||
const String& openFileDialogTitle,
|
||||
const String& saveFileDialogTitle);
|
||||
|
||||
/** Destructor. */
|
||||
~FileBasedDocument() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the changed() method has been called since the file was
|
||||
last saved or loaded.
|
||||
|
||||
@see setChangedFlag, changed
|
||||
*/
|
||||
bool hasChangedSinceSaved() const;
|
||||
|
||||
/** Called to indicate that the document has changed and needs saving.
|
||||
|
||||
This method will also trigger a change message to be sent out using the
|
||||
ChangeBroadcaster base class.
|
||||
|
||||
After calling the method, the hasChangedSinceSaved() method will return true, until
|
||||
it is reset either by saving to a file or using the setChangedFlag() method.
|
||||
|
||||
@see hasChangedSinceSaved, setChangedFlag
|
||||
*/
|
||||
virtual void changed();
|
||||
|
||||
/** Sets the state of the 'changed' flag.
|
||||
|
||||
The 'changed' flag is set to true when the changed() method is called - use this method
|
||||
to reset it or to set it without also broadcasting a change message.
|
||||
|
||||
@see changed, hasChangedSinceSaved
|
||||
*/
|
||||
void setChangedFlag (bool hasChanged);
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to open a file.
|
||||
|
||||
If the file opens correctly the document's file (see the getFile() method) is set
|
||||
to this new one; if it fails, the document's file is left unchanged, and optionally
|
||||
a message box is shown telling the user there was an error.
|
||||
|
||||
@returns A result indicating whether the new file loaded successfully, or the error
|
||||
message if it failed.
|
||||
@see loadDocument, loadFromUserSpecifiedFile
|
||||
*/
|
||||
Result loadFrom (const File& fileToLoadFrom,
|
||||
bool showMessageOnFailure,
|
||||
bool showWaitCursor = true);
|
||||
|
||||
/** Tries to open a file.
|
||||
|
||||
The callback is called with the result indicating whether the new file loaded
|
||||
successfully, or the error message if it failed.
|
||||
|
||||
If the file opens correctly the document's file (see the getFile() method) is set
|
||||
to this new one; if it fails, the document's file is left unchanged, and optionally
|
||||
a message box is shown telling the user there was an error.
|
||||
|
||||
@see loadDocumentAsync, loadFromUserSpecifiedFileAsync
|
||||
*/
|
||||
void loadFromAsync (const File& fileToLoadFrom,
|
||||
bool showMessageOnFailure,
|
||||
std::function<void (Result)> callback);
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** Asks the user for a file and tries to load it.
|
||||
|
||||
This will pop up a dialog box using the title, file extension and
|
||||
wildcard specified in the document's constructor, and asks the user
|
||||
for a file. If they pick one, the loadFrom() method is used to
|
||||
try to load it, optionally showing a message if it fails.
|
||||
|
||||
@returns a result indicating success; This will be a failure message if the user
|
||||
cancelled or if they picked a file which failed to load correctly
|
||||
@see loadFrom
|
||||
*/
|
||||
Result loadFromUserSpecifiedFile (bool showMessageOnFailure);
|
||||
#endif
|
||||
|
||||
/** Asks the user for a file and tries to load it.
|
||||
|
||||
This will pop up a dialog box using the title, file extension and
|
||||
wildcard specified in the document's constructor, and asks the user
|
||||
for a file. If they pick one, the loadFrom() method is used to
|
||||
try to load it, optionally showing a message if it fails. The result
|
||||
of the operation is provided in the callback function.
|
||||
|
||||
@see loadFrom
|
||||
*/
|
||||
void loadFromUserSpecifiedFileAsync (bool showMessageOnFailure, std::function<void (Result)> callback);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of possible outcomes of one of the save() methods
|
||||
*/
|
||||
enum SaveResult
|
||||
{
|
||||
savedOk = 0, /**< indicates that a file was saved successfully. */
|
||||
userCancelledSave, /**< indicates that the user aborted the save operation. */
|
||||
failedToWriteToFile /**< indicates that it tried to write to a file but this failed. */
|
||||
};
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** Tries to save the document to the last file it was saved or loaded from.
|
||||
|
||||
This will always try to write to the file, even if the document isn't flagged as
|
||||
having changed.
|
||||
|
||||
@param askUserForFileIfNotSpecified if there's no file currently specified and this is
|
||||
true, it will prompt the user to pick a file, as if
|
||||
saveAsInteractive() was called.
|
||||
@param showMessageOnFailure if true it will show a warning message when if the
|
||||
save operation fails
|
||||
@see saveIfNeededAndUserAgrees, saveAs, saveAsInteractive
|
||||
*/
|
||||
SaveResult save (bool askUserForFileIfNotSpecified,
|
||||
bool showMessageOnFailure);
|
||||
#endif
|
||||
|
||||
/** Tries to save the document to the last file it was saved or loaded from.
|
||||
|
||||
This will always try to write to the file, even if the document isn't flagged as
|
||||
having changed.
|
||||
|
||||
@param askUserForFileIfNotSpecified if there's no file currently specified and this is
|
||||
true, it will prompt the user to pick a file, as if
|
||||
saveAsInteractive() was called.
|
||||
@param showMessageOnFailure if true it will show a warning message when if the
|
||||
save operation fails
|
||||
@param callback called after the save operation with the result
|
||||
@see saveIfNeededAndUserAgrees, saveAs, saveAsInteractive
|
||||
*/
|
||||
void saveAsync (bool askUserForFileIfNotSpecified,
|
||||
bool showMessageOnFailure,
|
||||
std::function<void (SaveResult)> callback);
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** If the file needs saving, it'll ask the user if that's what they want to do, and save
|
||||
it if they say yes.
|
||||
|
||||
If you've got a document open and want to close it (e.g. to quit the app), this is the
|
||||
method to call.
|
||||
|
||||
If the document doesn't need saving it'll return the value savedOk so
|
||||
you can go ahead and delete the document.
|
||||
|
||||
If it does need saving it'll prompt the user, and if they say "discard changes" it'll
|
||||
return savedOk, so again, you can safely delete the document.
|
||||
|
||||
If the user clicks "cancel", it'll return userCancelledSave, so if you can abort the
|
||||
close-document operation.
|
||||
|
||||
And if they click "save changes", it'll try to save and either return savedOk, or
|
||||
failedToWriteToFile if there was a problem.
|
||||
|
||||
@see save, saveAs, saveAsInteractive
|
||||
*/
|
||||
SaveResult saveIfNeededAndUserAgrees();
|
||||
#endif
|
||||
|
||||
/** If the file needs saving, it'll ask the user if that's what they want to do, and save
|
||||
it if they say yes.
|
||||
|
||||
If you've got a document open and want to close it (e.g. to quit the app), this is the
|
||||
method to call.
|
||||
|
||||
If the document doesn't need saving the callback will be called with the value savedOk
|
||||
so you can go ahead and delete the document.
|
||||
|
||||
If it does need saving it'll prompt the user, and if they say "discard changes" the
|
||||
callback will be called with savedOk, so again, you can safely delete the document.
|
||||
|
||||
If the user clicks "cancel", the callback will be called with userCancelledSave, so
|
||||
you can abort the close-document operation.
|
||||
|
||||
And if they click "save changes", it'll try to save and the callback will be called
|
||||
with either savedOk, or failedToWriteToFile if there was a problem.
|
||||
|
||||
@see saveAsync, saveAsAsync, saveAsInteractiveAsync
|
||||
*/
|
||||
void saveIfNeededAndUserAgreesAsync (std::function<void (SaveResult)> callback);
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** Tries to save the document to a specified file.
|
||||
|
||||
If this succeeds, it'll also change the document's internal file (as returned by
|
||||
the getFile() method). If it fails, the file will be left unchanged.
|
||||
|
||||
@param newFile the file to try to write to
|
||||
@param warnAboutOverwritingExistingFiles if true and the file exists, it'll ask
|
||||
the user first if they want to overwrite it
|
||||
@param askUserForFileIfNotSpecified if the file is non-existent and this is true, it'll
|
||||
use the saveAsInteractive() method to ask the user for a
|
||||
filename
|
||||
@param showMessageOnFailure if true and the write operation fails, it'll show
|
||||
a message box to warn the user
|
||||
@param showWaitCursor if true, the 'wait' mouse cursor will be showin during
|
||||
saving
|
||||
@see saveIfNeededAndUserAgrees, save, saveAsInteractive
|
||||
*/
|
||||
SaveResult saveAs (const File& newFile,
|
||||
bool warnAboutOverwritingExistingFiles,
|
||||
bool askUserForFileIfNotSpecified,
|
||||
bool showMessageOnFailure,
|
||||
bool showWaitCursor = true);
|
||||
#endif
|
||||
|
||||
/** Tries to save the document to a specified file.
|
||||
|
||||
If this succeeds, it'll also change the document's internal file (as returned by
|
||||
the getFile() method). If it fails, the file will be left unchanged.
|
||||
|
||||
@param newFile the file to try to write to
|
||||
@param warnAboutOverwritingExistingFiles if true and the file exists, it'll ask the user
|
||||
first if they want to overwrite it
|
||||
@param askUserForFileIfNotSpecified if the file is non-existent and this is true, it'll
|
||||
use the saveAsInteractive() method to ask the user
|
||||
for a filename
|
||||
@param showMessageOnFailure if true and the write operation fails, it'll show
|
||||
a message box to warn the user
|
||||
@param callback called with the result of the save operation
|
||||
|
||||
@see saveIfNeededAndUserAgreesAsync, saveAsync, saveAsInteractiveAsync
|
||||
*/
|
||||
void saveAsAsync (const File& newFile,
|
||||
bool warnAboutOverwritingExistingFiles,
|
||||
bool askUserForFileIfNotSpecified,
|
||||
bool showMessageOnFailure,
|
||||
std::function<void (SaveResult)> callback);
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** Prompts the user for a filename and tries to save to it.
|
||||
|
||||
This will pop up a dialog box using the title, file extension and
|
||||
wildcard specified in the document's constructor, and asks the user
|
||||
for a file. If they pick one, the saveAs() method is used to try to save
|
||||
to this file.
|
||||
|
||||
@param warnAboutOverwritingExistingFiles if true and the file exists, it'll ask
|
||||
the user first if they want to overwrite it
|
||||
@see saveIfNeededAndUserAgrees, save, saveAs
|
||||
*/
|
||||
SaveResult saveAsInteractive (bool warnAboutOverwritingExistingFiles);
|
||||
#endif
|
||||
|
||||
/** Prompts the user for a filename and tries to save to it.
|
||||
|
||||
This will pop up a dialog box using the title, file extension and
|
||||
wildcard specified in the document's constructor, and asks the user
|
||||
for a file. If they pick one, the saveAs() method is used to try to save
|
||||
to this file.
|
||||
|
||||
@param warnAboutOverwritingExistingFiles if true and the file exists, it'll ask
|
||||
the user first if they want to overwrite it
|
||||
@param callback called with the result of the save operation
|
||||
@see saveIfNeededAndUserAgreesAsync, saveAsync, saveAsAsync
|
||||
*/
|
||||
void saveAsInteractiveAsync (bool warnAboutOverwritingExistingFiles,
|
||||
std::function<void (SaveResult)> callback);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the file that this document was last successfully saved or loaded from.
|
||||
|
||||
When the document object is created, this will be set to File().
|
||||
|
||||
It is changed when one of the load or save methods is used, or when setFile()
|
||||
is used to explicitly set it.
|
||||
*/
|
||||
const File& getFile() const;
|
||||
|
||||
/** Sets the file that this document thinks it was loaded from.
|
||||
|
||||
This won't actually load anything - it just changes the file stored internally.
|
||||
|
||||
@see getFile
|
||||
*/
|
||||
void setFile (const File& newFile);
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Overload this to return the title of the document.
|
||||
|
||||
This is used in message boxes, filenames and file choosers, so it should be
|
||||
something sensible.
|
||||
*/
|
||||
virtual String getDocumentTitle() = 0;
|
||||
|
||||
/** This method should try to load your document from the given file.
|
||||
@returns a Result object to indicate the whether there was an error.
|
||||
*/
|
||||
virtual Result loadDocument (const File& file) = 0;
|
||||
|
||||
/** This method should try to load your document from the given file, then
|
||||
call the provided callback on the message thread, passing the result of the load.
|
||||
|
||||
By default, this will synchronously call through to loadDocument.
|
||||
|
||||
For longer-running load operations, you may wish to override this function to
|
||||
run the load on a background thread, and then to call the callback later on the
|
||||
message thread to signal that the load has completed.
|
||||
*/
|
||||
virtual void loadDocumentAsync (const File& file, std::function<void (Result)> callback);
|
||||
|
||||
/** This method should try to write your document to the given file.
|
||||
@returns a Result object to indicate the whether there was an error.
|
||||
*/
|
||||
virtual Result saveDocument (const File& file) = 0;
|
||||
|
||||
/** This method should try to write your document to the given file, then
|
||||
call the provided callback on the message thread, passing the result of the write.
|
||||
|
||||
By default, this will synchronously call through to saveDocument.
|
||||
|
||||
For longer-running save operations, you may wish to override this function to
|
||||
run the save on a background thread, and then to call the callback later on the
|
||||
message thread to signal that the save has completed.
|
||||
*/
|
||||
virtual void saveDocumentAsync (const File& file, std::function<void (Result)> callback);
|
||||
|
||||
/** This is used for dialog boxes to make them open at the last folder you
|
||||
were using.
|
||||
|
||||
getLastDocumentOpened() and setLastDocumentOpened() are used to store
|
||||
the last document that was used - you might want to store this value
|
||||
in a static variable, or even in your application's properties. It should
|
||||
be a global setting rather than a property of this object.
|
||||
|
||||
This method works very well in conjunction with a RecentlyOpenedFilesList
|
||||
object to manage your recent-files list.
|
||||
|
||||
As a default value, it's ok to return File(), and the document object will
|
||||
use a sensible one instead.
|
||||
|
||||
@see RecentlyOpenedFilesList
|
||||
*/
|
||||
virtual File getLastDocumentOpened() = 0;
|
||||
|
||||
/** This is used for dialog boxes to make them open at the last folder you
|
||||
were using.
|
||||
|
||||
getLastDocumentOpened() and setLastDocumentOpened() are used to store
|
||||
the last document that was used - you might want to store this value
|
||||
in a static variable, or even in your application's properties. It should
|
||||
be a global setting rather than a property of this object.
|
||||
|
||||
This method works very well in conjunction with a RecentlyOpenedFilesList
|
||||
object to manage your recent-files list.
|
||||
|
||||
@see RecentlyOpenedFilesList
|
||||
*/
|
||||
virtual void setLastDocumentOpened (const File& file) = 0;
|
||||
|
||||
/** This is called by saveAsInteractiveAsync() to allow you to optionally customise the
|
||||
filename that the user is presented with in the save dialog.
|
||||
The defaultFile parameter is an initial suggestion based on what the class knows
|
||||
about the current document - you can return a variation on this file with a different
|
||||
extension, etc, or just return something completely different.
|
||||
*/
|
||||
virtual File getSuggestedSaveAsFile (const File& defaultFile);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class Pimpl;
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileBasedDocument)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_WINDOWS || DOXYGEN
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A Windows-specific class that can create and embed an ActiveX control inside
|
||||
itself.
|
||||
|
||||
To use it, create one of these, put it in place and make sure it's visible in a
|
||||
window, then use createControl() to instantiate an ActiveX control. The control
|
||||
will then be moved and resized to follow the movements of this component.
|
||||
|
||||
Of course, since the control is a heavyweight window, it'll obliterate any
|
||||
JUCE components that may overlap this component, but that's life.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ActiveXControlComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Create an initially-empty container. */
|
||||
ActiveXControlComponent();
|
||||
|
||||
/** Destructor. */
|
||||
~ActiveXControlComponent() override;
|
||||
|
||||
/** Tries to create an ActiveX control and embed it in this peer.
|
||||
|
||||
The peer controlIID is a pointer to an IID structure - it's treated
|
||||
as a void* because when including the JUCE headers, you might not always
|
||||
have included windows.h first, in which case IID wouldn't be defined.
|
||||
|
||||
e.g. @code
|
||||
const IID myIID = __uuidof (QTControl);
|
||||
myControlComp->createControl (&myIID);
|
||||
@endcode
|
||||
*/
|
||||
bool createControl (const void* controlIID);
|
||||
|
||||
/** Deletes the ActiveX control, if one has been created.
|
||||
*/
|
||||
void deleteControl();
|
||||
|
||||
/** Returns true if a control is currently in use. */
|
||||
bool isControlOpen() const noexcept { return control != nullptr; }
|
||||
|
||||
/** Does a QueryInterface call on the embedded control object.
|
||||
|
||||
This allows you to cast the control to whatever type of COM object you need.
|
||||
|
||||
The iid parameter is a pointer to an IID structure - it's treated
|
||||
as a void* because when including the JUCE headers, you might not always
|
||||
have included windows.h first, in which case IID wouldn't be defined, but
|
||||
you should just pass a pointer to an IID.
|
||||
|
||||
e.g. @code
|
||||
const IID iid = __uuidof (IOleWindow);
|
||||
|
||||
IOleWindow* oleWindow = (IOleWindow*) myControlComp->queryInterface (&iid);
|
||||
|
||||
if (oleWindow != nullptr)
|
||||
{
|
||||
HWND hwnd;
|
||||
oleWindow->GetWindow (&hwnd);
|
||||
|
||||
...
|
||||
|
||||
oleWindow->Release();
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
void* queryInterface (const void* iid) const;
|
||||
|
||||
/** Set this to false to stop mouse events being allowed through to the control.
|
||||
*/
|
||||
void setMouseEventsAllowed (bool eventsCanReachControl);
|
||||
|
||||
/** Returns true if mouse events are allowed to get through to the control.
|
||||
*/
|
||||
bool areMouseEventsAllowed() const noexcept { return mouseEventsAllowed; }
|
||||
|
||||
//==============================================================================
|
||||
/** Set an instance of IDispatch where dispatch events should be delivered to
|
||||
*/
|
||||
void setEventHandler (void* eventHandler);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
|
||||
/** @internal */
|
||||
intptr_t offerEventToActiveXControl (void*);
|
||||
static intptr_t offerEventToActiveXControlStatic (void*);
|
||||
|
||||
private:
|
||||
class Pimpl;
|
||||
std::unique_ptr<Pimpl> control;
|
||||
bool mouseEventsAllowed = true;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ActiveXControlComponent)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_ANDROID || DOXYGEN
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An Android-specific class that can create and embed a View inside itself.
|
||||
|
||||
To use it, create one of these, put it in place and make sure it's visible in a
|
||||
window, then use setView() to assign a View to it. The view will then be
|
||||
moved and resized to follow the movements of this component.
|
||||
|
||||
Of course, since the view is a native object, it'll obliterate any
|
||||
JUCE components that may overlap this component, but that's life.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API AndroidViewComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Create an initially-empty container */
|
||||
AndroidViewComponent();
|
||||
|
||||
/** Destructor. */
|
||||
~AndroidViewComponent() override;
|
||||
|
||||
/** Assigns a View to this peer.
|
||||
|
||||
The view will be retained and released by this component for as long as
|
||||
it is needed. To remove the current view, just call setView (nullptr).
|
||||
*/
|
||||
void setView (void* uiView);
|
||||
|
||||
/** Returns the current View. */
|
||||
void* getView() const;
|
||||
|
||||
/** Resizes this component to fit the view that it contains. */
|
||||
void resizeToFitView();
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
class Pimpl;
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidViewComponent)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_WINDOWS || DOXYGEN
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A Windows-specific class that can create and embed a HWND inside itself.
|
||||
|
||||
To use it, create one of these, put it in place and make sure it's visible in a
|
||||
window, then use setHWND() to assign a HWND to it. The window will then be
|
||||
moved and resized to follow the movements of this component.
|
||||
|
||||
Of course, since the window is a native object, it'll obliterate any
|
||||
JUCE components that may overlap this component, but that's life.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API HWNDComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Create an initially-empty container. */
|
||||
HWNDComponent();
|
||||
|
||||
/** Destructor. */
|
||||
~HWNDComponent() override;
|
||||
|
||||
/** Assigns a HWND to this peer.
|
||||
|
||||
The window will be retained and released by this component for as long as
|
||||
it is needed. To remove the current HWND, just call setHWND (nullptr).
|
||||
|
||||
Note: A void* is used here to avoid including the Windows headers as
|
||||
part of JuceHeader.h, but the method expects a HWND.
|
||||
*/
|
||||
void setHWND (void* hwnd);
|
||||
|
||||
/** Returns the current HWND.
|
||||
|
||||
Note: A void* is returned here to avoid the needing to include the Windows
|
||||
headers, so you should just cast the return value to a HWND.
|
||||
*/
|
||||
void* getHWND() const;
|
||||
|
||||
/** Resizes this component to fit the HWND that it contains. */
|
||||
void resizeToFit();
|
||||
|
||||
/** Forces the bounds of the HWND to match the bounds of this component. */
|
||||
void updateHWNDBounds();
|
||||
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
|
||||
private:
|
||||
class Pimpl;
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HWNDComponent)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_MAC || DOXYGEN
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A Mac-specific class that can create and embed an NSView inside itself.
|
||||
|
||||
To use it, create one of these, put it in place and make sure it's visible in a
|
||||
window, then use setView() to assign an NSView to it. The view will then be
|
||||
moved and resized to follow the movements of this component.
|
||||
|
||||
Of course, since the view is a native object, it'll obliterate any
|
||||
JUCE components that may overlap this component, but that's life.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API NSViewComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Create an initially-empty container. */
|
||||
NSViewComponent();
|
||||
|
||||
/** Destructor. */
|
||||
~NSViewComponent() override;
|
||||
|
||||
/** Assigns an NSView to this peer.
|
||||
|
||||
The view will be retained and released by this component for as long as
|
||||
it is needed. To remove the current view, just call setView (nullptr).
|
||||
|
||||
Note: A void* is used here to avoid including the cocoa headers as
|
||||
part of JuceHeader.h, but the method expects an NSView*.
|
||||
*/
|
||||
void setView (void* nsView);
|
||||
|
||||
/** Returns the current NSView.
|
||||
|
||||
Note: A void* is returned here to avoid the needing to include the cocoa
|
||||
headers, so you should just cast the return value to an NSView*.
|
||||
*/
|
||||
void* getView() const;
|
||||
|
||||
/** Resizes this component to fit the view that it contains. */
|
||||
void resizeToFitView();
|
||||
|
||||
/** Resizes the NSView to match the bounds of this component.
|
||||
|
||||
Most of the time, this will be done for you automatically.
|
||||
*/
|
||||
void resizeViewToFit();
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void alphaChanged() override;
|
||||
/** @internal */
|
||||
static ReferenceCountedObject* attachViewToComponent (Component&, void*);
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
ReferenceCountedObjectPtr<ReferenceCountedObject> attachment;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewComponent)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_IOS || DOXYGEN
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An iOS-specific class that can create and embed an UIView inside itself.
|
||||
|
||||
To use it, create one of these, put it in place and make sure it's visible in a
|
||||
window, then use setView() to assign a UIView to it. The view will then be
|
||||
moved and resized to follow the movements of this component.
|
||||
|
||||
Of course, since the view is a native object, it'll obliterate any
|
||||
JUCE components that may overlap this component, but that's life.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API UIViewComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Create an initially-empty container. */
|
||||
UIViewComponent();
|
||||
|
||||
/** Destructor. */
|
||||
~UIViewComponent() override;
|
||||
|
||||
/** Assigns an UIView to this peer.
|
||||
|
||||
The view will be retained and released by this component for as long as
|
||||
it is needed. To remove the current view, just call setView (nullptr).
|
||||
|
||||
Note: A void* is used here to avoid including the cocoa headers as
|
||||
part of JuceHeader.h, but the method expects an UIView*.
|
||||
*/
|
||||
void setView (void* uiView);
|
||||
|
||||
/** Returns the current UIView.
|
||||
|
||||
Note: A void* is returned here to avoid the needing to include the cocoa
|
||||
headers, so you should just cast the return value to an UIView*.
|
||||
*/
|
||||
void* getView() const;
|
||||
|
||||
/** Resizes this component to fit the view that it contains. */
|
||||
void resizeToFitView();
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
|
||||
private:
|
||||
class Pimpl;
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponent)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/** @internal */
|
||||
bool juce_handleXEmbedEvent (ComponentPeer*, void*);
|
||||
/** @internal */
|
||||
unsigned long juce_getCurrentFocusWindow (ComponentPeer*);
|
||||
|
||||
#if JUCE_LINUX || JUCE_BSD || DOXYGEN
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A Linux-specific class that can embed a foreign X11 widget.
|
||||
|
||||
Use this class to embed a foreign X11 widget from other toolkits such as
|
||||
GTK+ or QT.
|
||||
|
||||
There are two ways to initiate the Xembed protocol. Either the client creates
|
||||
a window and passes this to the host (client initiated) or the host
|
||||
creates a window in which the client can reparent it's client widget
|
||||
(host initiated). XEmbedComponent supports both protocol types.
|
||||
|
||||
This is how you embed a GTK+ widget: if you are using the client
|
||||
initiated version of the protocol, then create a new gtk widget with
|
||||
gtk_plug_new (0). Then query the window id of the plug via gtk_plug_get_id().
|
||||
Pass this id to the constructor of this class.
|
||||
|
||||
If you are using the host initiated version of the protocol, then first create
|
||||
the XEmbedComponent using the default constructor. Use getHostWindowID to get
|
||||
the window id of the host, use this to construct your gtk plug via gtk_plug_new.
|
||||
|
||||
A similar approach can be used to embed QT widgets via QT's QX11EmbedWidget
|
||||
class.
|
||||
|
||||
Other toolkits or raw X11 widgets should follow the X11 embed protocol:
|
||||
https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class XEmbedComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
|
||||
/** Creates a JUCE component wrapping a foreign widget
|
||||
|
||||
Use this constructor if you are using the host initiated version
|
||||
of the XEmbedProtocol. When using this version of the protocol
|
||||
you must call getHostWindowID() and pass this id to the foreign toolkit.
|
||||
*/
|
||||
XEmbedComponent (bool wantsKeyboardFocus = true,
|
||||
bool allowForeignWidgetToResizeComponent = false);
|
||||
|
||||
/** Create a JUCE component wrapping the foreign widget with id wID
|
||||
|
||||
Use this constructor if you are using the client initiated version
|
||||
of the XEmbedProtocol.
|
||||
*/
|
||||
XEmbedComponent (unsigned long wID, bool wantsKeyboardFocus = true,
|
||||
bool allowForeignWidgetToResizeComponent = false);
|
||||
|
||||
|
||||
/** Destructor. */
|
||||
~XEmbedComponent() override;
|
||||
|
||||
/** Use this method to retrieve the host's window id when using the
|
||||
host initiated version of the XEmbedProtocol
|
||||
*/
|
||||
unsigned long getHostWindowID();
|
||||
|
||||
/** Removes the client window from the host. */
|
||||
void removeClient();
|
||||
|
||||
/** Forces the embedded window to match the current size of this component. */
|
||||
void updateEmbeddedBounds();
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
void focusGainedWithDirection (FocusChangeType, FocusChangeDirection) override;
|
||||
void focusLost (FocusChangeType) override;
|
||||
void broughtToFront() override;
|
||||
|
||||
private:
|
||||
friend bool juce::juce_handleXEmbedEvent (ComponentPeer*, void*);
|
||||
friend unsigned long juce_getCurrentFocusWindow (ComponentPeer*);
|
||||
|
||||
class Pimpl;
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifdef JUCE_GUI_EXTRA_H_INCLUDED
|
||||
/* When you add this cpp file to your project, you mustn't include it in a file where you've
|
||||
already included any other headers - just put it inside a file on its own, possibly with your config
|
||||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
|
||||
header files that the compiler may be using.
|
||||
*/
|
||||
#error "Incorrect use of JUCE cpp file"
|
||||
#endif
|
||||
|
||||
#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1
|
||||
#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1
|
||||
#define JUCE_CORE_INCLUDE_JNI_HELPERS 1
|
||||
#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1
|
||||
#define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1
|
||||
#define JUCE_GRAPHICS_INCLUDE_COREGRAPHICS_HELPERS 1
|
||||
#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1
|
||||
#define JUCE_GUI_BASICS_INCLUDE_SCOPED_THREAD_DPI_AWARENESS_SETTER 1
|
||||
|
||||
#ifndef JUCE_PUSH_NOTIFICATIONS
|
||||
#define JUCE_PUSH_NOTIFICATIONS 0
|
||||
#endif
|
||||
|
||||
#include "juce_gui_extra.h"
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MAC
|
||||
#if JUCE_WEB_BROWSER
|
||||
#import <WebKit/WebKit.h>
|
||||
#endif
|
||||
#import <IOKit/IOKitLib.h>
|
||||
#import <IOKit/IOCFPlugIn.h>
|
||||
#import <IOKit/hid/IOHIDLib.h>
|
||||
#import <IOKit/hid/IOHIDKeys.h>
|
||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||
|
||||
#if JUCE_PUSH_NOTIFICATIONS
|
||||
#import <Foundation/NSUserNotification.h>
|
||||
|
||||
#include "native/juce_PushNotifications_mac.cpp"
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#elif JUCE_IOS
|
||||
#if JUCE_WEB_BROWSER
|
||||
#import <WebKit/WebKit.h>
|
||||
#endif
|
||||
|
||||
#if JUCE_PUSH_NOTIFICATIONS
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
#include "native/juce_PushNotifications_ios.cpp"
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#elif JUCE_ANDROID
|
||||
#if JUCE_PUSH_NOTIFICATIONS
|
||||
#include "native/juce_PushNotifications_android.cpp"
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#elif JUCE_WINDOWS
|
||||
#include <windowsx.h>
|
||||
#include <vfw.h>
|
||||
#include <commdlg.h>
|
||||
|
||||
#if JUCE_WEB_BROWSER
|
||||
#include <exdisp.h>
|
||||
#include <exdispid.h>
|
||||
|
||||
#if JUCE_USE_WIN_WEBVIEW2
|
||||
#include <windows.foundation.h>
|
||||
#include <windows.foundation.collections.h>
|
||||
#include <winuser.h>
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4265)
|
||||
#include <wrl.h>
|
||||
#include <wrl/wrappers/corewrappers.h>
|
||||
JUCE_END_IGNORE_WARNINGS_MSVC
|
||||
|
||||
#include "WebView2.h"
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4458)
|
||||
#include "WebView2EnvironmentOptions.h"
|
||||
JUCE_END_IGNORE_WARNINGS_MSVC
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#elif (JUCE_LINUX || JUCE_BSD) && JUCE_WEB_BROWSER
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wzero-as-null-pointer-constant", "-Wparentheses", "-Wdeprecated-declarations")
|
||||
|
||||
// If you're missing this header, you need to install the webkit2gtk-4.1 or webkit2gtk-4.0 package
|
||||
#include <gtk/gtk.h>
|
||||
#include <gtk/gtkx.h>
|
||||
#include <glib-unix.h>
|
||||
#include <webkit2/webkit2.h>
|
||||
#include <jsc/jsc.h>
|
||||
#include <libsoup/soup.h>
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#include "documents/juce_FileBasedDocument.cpp"
|
||||
#include "code_editor/juce_CodeDocument.cpp"
|
||||
#include "code_editor/juce_CodeEditorComponent.cpp"
|
||||
#include "code_editor/juce_CPlusPlusCodeTokeniser.cpp"
|
||||
#include "code_editor/juce_XMLCodeTokeniser.cpp"
|
||||
#include "code_editor/juce_LuaCodeTokeniser.cpp"
|
||||
#include "misc/juce_BubbleMessageComponent.cpp"
|
||||
#include "misc/juce_ColourSelector.cpp"
|
||||
#include "misc/juce_KeyMappingEditorComponent.cpp"
|
||||
#include "misc/juce_PreferencesPanel.cpp"
|
||||
#include "misc/juce_PushNotifications.cpp"
|
||||
#include "misc/juce_RecentlyOpenedFilesList.cpp"
|
||||
#include "misc/juce_SplashScreen.cpp"
|
||||
|
||||
#if JUCE_MAC
|
||||
#include "native/juce_SystemTrayIcon_mac.cpp"
|
||||
#elif JUCE_WINDOWS
|
||||
#include "native/juce_ActiveXComponent_windows.cpp"
|
||||
#include "native/juce_SystemTrayIcon_windows.cpp"
|
||||
#elif JUCE_LINUX || JUCE_BSD
|
||||
#include "native/juce_SystemTrayIcon_linux.cpp"
|
||||
#endif
|
||||
|
||||
#include "misc/juce_SystemTrayIconComponent.cpp"
|
||||
#include "misc/juce_LiveConstantEditor.cpp"
|
||||
#include "misc/juce_AnimatedAppComponent.cpp"
|
||||
#include "misc/juce_WebBrowserComponent.cpp"
|
||||
#include "misc/juce_WebControlRelays.cpp"
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MAC || JUCE_IOS
|
||||
|
||||
#if JUCE_MAC
|
||||
#include "native/juce_NSViewFrameWatcher_mac.h"
|
||||
#include "native/juce_NSViewComponent_mac.mm"
|
||||
#include "native/juce_AppleRemote_mac.mm"
|
||||
#endif
|
||||
|
||||
#if JUCE_IOS
|
||||
#include "native/juce_UIViewComponent_ios.mm"
|
||||
#endif
|
||||
|
||||
#if JUCE_WEB_BROWSER
|
||||
#include "native/juce_WebBrowserComponent_mac.mm"
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#elif JUCE_WINDOWS
|
||||
#include "native/juce_HWNDComponent_windows.cpp"
|
||||
#if JUCE_WEB_BROWSER
|
||||
#include "native/juce_WebBrowserComponent_windows.cpp"
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#elif JUCE_LINUX || JUCE_BSD
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wzero-as-null-pointer-constant")
|
||||
|
||||
#include <juce_gui_basics/native/juce_ScopedWindowAssociation_linux.h>
|
||||
#include "native/juce_XEmbedComponent_linux.cpp"
|
||||
|
||||
#if JUCE_WEB_BROWSER
|
||||
#if JUCE_USE_EXTERNAL_TEMPORARY_SUBPROCESS
|
||||
#include "juce_LinuxSubprocessHelperBinaryData.h"
|
||||
#endif
|
||||
|
||||
#include "native/juce_WebBrowserComponent_linux.cpp"
|
||||
#endif
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
//==============================================================================
|
||||
#elif JUCE_ANDROID
|
||||
#include "native/juce_AndroidViewComponent.cpp"
|
||||
|
||||
#if JUCE_WEB_BROWSER
|
||||
#include "native/juce_WebBrowserComponent_android.cpp"
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this module, and is read by
|
||||
the Projucer to automatically generate project code that uses it.
|
||||
For details about the syntax and how to create or use a module, see the
|
||||
JUCE Module Format.md file.
|
||||
|
||||
|
||||
BEGIN_JUCE_MODULE_DECLARATION
|
||||
|
||||
ID: juce_gui_extra
|
||||
vendor: juce
|
||||
version: 8.0.7
|
||||
name: JUCE extended GUI classes
|
||||
description: Miscellaneous GUI classes for specialised tasks.
|
||||
website: http://www.juce.com/juce
|
||||
license: AGPLv3/Commercial
|
||||
minimumCppStandard: 17
|
||||
|
||||
dependencies: juce_gui_basics
|
||||
OSXFrameworks: WebKit
|
||||
iOSFrameworks: WebKit
|
||||
WeakiOSFrameworks: UserNotifications
|
||||
WeakMacOSFrameworks: UserNotifications
|
||||
|
||||
END_JUCE_MODULE_DECLARATION
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
#define JUCE_GUI_EXTRA_H_INCLUDED
|
||||
|
||||
#include <juce_gui_basics/juce_gui_basics.h>
|
||||
|
||||
//==============================================================================
|
||||
/** Config: JUCE_WEB_BROWSER
|
||||
This lets you disable the WebBrowserComponent class.
|
||||
If you're not using any embedded web-pages, turning this off may reduce your code size.
|
||||
*/
|
||||
#ifndef JUCE_WEB_BROWSER
|
||||
#define JUCE_WEB_BROWSER 1
|
||||
#endif
|
||||
|
||||
/** Config: JUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKING
|
||||
Enables the use of the Microsoft Edge (Chromium) WebView2 browser on Windows.
|
||||
|
||||
If using the Projucer, the Microsoft.Web.WebView2 package will be added to the
|
||||
project solution if this flag is enabled. If you are building using CMake you
|
||||
will need to manually add the package via the NuGet package manager.
|
||||
|
||||
Using this flag requires statically linking against WebView2LoaderStatic.lib,
|
||||
which at this time is only available through the NuGet package, but is missing
|
||||
in VCPKG.
|
||||
|
||||
In addition to enabling this macro, you will need to use the
|
||||
WebBrowserComponent::Options::Backend::webview2 option when instantiating the
|
||||
WebBrowserComponent.
|
||||
*/
|
||||
#ifndef JUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKING
|
||||
#define JUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKING 0
|
||||
#else
|
||||
#define JUCE_USE_WIN_WEBVIEW2 1
|
||||
#endif
|
||||
|
||||
/** Config: JUCE_USE_WIN_WEBVIEW2
|
||||
Enables the use of the Microsoft Edge (Chromium) WebView2 browser on Windows.
|
||||
|
||||
If using the Projucer, the Microsoft.Web.WebView2 package will be added to the
|
||||
project solution if this flag is enabled. If you are building using CMake you
|
||||
will need to manually add the package via the Visual Studio package manager.
|
||||
|
||||
If the WITH_STATIC_LINKING variant of this flag is also set, you must statically
|
||||
link against WebView2LoaderStatic.lib, otherwise dynamic loading will be used.
|
||||
|
||||
See more about dynamic linking in the documentation of
|
||||
WebBrowserComponent::Options::WinWebView2::withDLLLocation().
|
||||
|
||||
In addition to enabling this macro, you will need to use the
|
||||
WebBrowserComponent::Options::Backend::webview2 option when instantiating the
|
||||
WebBrowserComponent.
|
||||
*/
|
||||
#ifndef JUCE_USE_WIN_WEBVIEW2
|
||||
#define JUCE_USE_WIN_WEBVIEW2 0
|
||||
#endif
|
||||
|
||||
/** Config: JUCE_ENABLE_LIVE_CONSTANT_EDITOR
|
||||
This lets you turn on the JUCE_ENABLE_LIVE_CONSTANT_EDITOR support (desktop only). By default
|
||||
this will be enabled for debug builds and disabled for release builds. See the documentation
|
||||
for that macro for more details.
|
||||
*/
|
||||
#ifndef JUCE_ENABLE_LIVE_CONSTANT_EDITOR
|
||||
#if JUCE_DEBUG && ! (JUCE_IOS || JUCE_ANDROID)
|
||||
#define JUCE_ENABLE_LIVE_CONSTANT_EDITOR 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#include "documents/juce_FileBasedDocument.h"
|
||||
#include "code_editor/juce_CodeDocument.h"
|
||||
#include "code_editor/juce_CodeEditorComponent.h"
|
||||
#include "code_editor/juce_CodeTokeniser.h"
|
||||
#include "code_editor/juce_CPlusPlusCodeTokeniser.h"
|
||||
#include "code_editor/juce_CPlusPlusCodeTokeniserFunctions.h"
|
||||
#include "code_editor/juce_XMLCodeTokeniser.h"
|
||||
#include "code_editor/juce_LuaCodeTokeniser.h"
|
||||
#include "embedding/juce_ActiveXControlComponent.h"
|
||||
#include "embedding/juce_AndroidViewComponent.h"
|
||||
#include "embedding/juce_NSViewComponent.h"
|
||||
#include "embedding/juce_UIViewComponent.h"
|
||||
#include "embedding/juce_XEmbedComponent.h"
|
||||
#include "embedding/juce_HWNDComponent.h"
|
||||
#include "misc/juce_AppleRemote.h"
|
||||
#include "misc/juce_BubbleMessageComponent.h"
|
||||
#include "misc/juce_ColourSelector.h"
|
||||
#include "misc/juce_KeyMappingEditorComponent.h"
|
||||
#include "misc/juce_PreferencesPanel.h"
|
||||
#include "misc/juce_PushNotifications.h"
|
||||
#include "misc/juce_RecentlyOpenedFilesList.h"
|
||||
#include "misc/juce_SplashScreen.h"
|
||||
#include "misc/juce_SystemTrayIconComponent.h"
|
||||
#include "misc/juce_WebBrowserComponent.h"
|
||||
#include "misc/juce_LiveConstantEditor.h"
|
||||
#include "misc/juce_AnimatedAppComponent.h"
|
||||
#include "detail/juce_WebControlRelayEvents.h"
|
||||
#include "misc/juce_WebControlRelays.h"
|
||||
#include "misc/juce_WebControlParameterIndexReceiver.h"
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#include "juce_gui_extra.cpp"
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
AnimatedAppComponent::AnimatedAppComponent()
|
||||
{
|
||||
setOpaque (true);
|
||||
}
|
||||
|
||||
void AnimatedAppComponent::setFramesPerSecond (int framesPerSecondIn)
|
||||
{
|
||||
jassert (0 < framesPerSecond && framesPerSecond < 1000);
|
||||
framesPerSecond = framesPerSecondIn;
|
||||
updateSync();
|
||||
}
|
||||
|
||||
void AnimatedAppComponent::updateSync()
|
||||
{
|
||||
if (useVBlank)
|
||||
{
|
||||
stopTimer();
|
||||
|
||||
if (vBlankAttachment.isEmpty())
|
||||
vBlankAttachment = { this, [this] { timerCallback(); } };
|
||||
}
|
||||
else
|
||||
{
|
||||
vBlankAttachment = {};
|
||||
|
||||
const auto interval = 1000 / framesPerSecond;
|
||||
|
||||
if (getTimerInterval() != interval)
|
||||
startTimer (interval);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedAppComponent::setSynchroniseToVBlank (bool syncToVBlank)
|
||||
{
|
||||
useVBlank = syncToVBlank;
|
||||
updateSync();
|
||||
}
|
||||
|
||||
int AnimatedAppComponent::getMillisecondsSinceLastUpdate() const noexcept
|
||||
{
|
||||
return (int) (Time::getCurrentTime() - lastUpdateTime).inMilliseconds();
|
||||
}
|
||||
|
||||
void AnimatedAppComponent::timerCallback()
|
||||
{
|
||||
++totalUpdates;
|
||||
update();
|
||||
repaint();
|
||||
lastUpdateTime = Time::getCurrentTime();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A base class for writing simple one-page graphical apps.
|
||||
|
||||
A subclass can inherit from this and implement just a few methods such as
|
||||
paint() and mouse-handling. The base class provides some simple abstractions
|
||||
to take care of continuously repainting itself.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API AnimatedAppComponent : public Component,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
AnimatedAppComponent();
|
||||
|
||||
/** Your subclass can call this to start a timer running which will
|
||||
call update() and repaint the component at the given frequency.
|
||||
*/
|
||||
void setFramesPerSecond (int framesPerSecond);
|
||||
|
||||
/** You can use this function to synchronise animation updates with the current display's vblank
|
||||
events. When this mode is enabled the value passed to setFramesPerSecond() is ignored.
|
||||
*/
|
||||
void setSynchroniseToVBlank (bool syncToVBlank);
|
||||
|
||||
/** Called periodically, at the frequency specified by setFramesPerSecond().
|
||||
This is a the best place to do things like advancing animation parameters,
|
||||
checking the mouse position, etc.
|
||||
*/
|
||||
virtual void update() = 0;
|
||||
|
||||
/** Returns the number of times that update() has been called since the component
|
||||
started running.
|
||||
*/
|
||||
int getFrameCounter() const noexcept { return totalUpdates; }
|
||||
|
||||
/** When called from update(), this returns the number of milliseconds since the
|
||||
last update call.
|
||||
This might be useful for accurately timing animations, etc.
|
||||
*/
|
||||
int getMillisecondsSinceLastUpdate() const noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void updateSync();
|
||||
|
||||
Time lastUpdateTime = Time::getCurrentTime();
|
||||
int totalUpdates = 0;
|
||||
int framesPerSecond = 60;
|
||||
bool useVBlank = false;
|
||||
VBlankAttachment vBlankAttachment;
|
||||
|
||||
void timerCallback() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnimatedAppComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MAC || DOXYGEN
|
||||
/**
|
||||
Receives events from an Apple IR remote control device (Only available in OSX!).
|
||||
|
||||
To use it, just create a subclass of this class, implementing the buttonPressed()
|
||||
callback, then call start() and stop() to start or stop receiving events.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API AppleRemoteDevice
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
AppleRemoteDevice();
|
||||
virtual ~AppleRemoteDevice();
|
||||
|
||||
//==============================================================================
|
||||
/** The set of buttons that may be pressed.
|
||||
@see buttonPressed
|
||||
*/
|
||||
enum ButtonType
|
||||
{
|
||||
menuButton = 0, /**< The menu button (if it's held for a short time). */
|
||||
playButton, /**< The play button. */
|
||||
plusButton, /**< The plus or volume-up button. */
|
||||
minusButton, /**< The minus or volume-down button. */
|
||||
rightButton, /**< The right button (if it's held for a short time). */
|
||||
leftButton, /**< The left button (if it's held for a short time). */
|
||||
rightButton_Long, /**< The right button (if it's held for a long time). */
|
||||
leftButton_Long, /**< The menu button (if it's held for a long time). */
|
||||
menuButton_Long, /**< The menu button (if it's held for a long time). */
|
||||
playButtonSleepMode,
|
||||
switched
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Override this method to receive the callback about a button press.
|
||||
|
||||
The callback will happen on the application's message thread.
|
||||
|
||||
Some buttons trigger matching up and down events, in which the isDown parameter
|
||||
will be true and then false. Others only send a single event when the
|
||||
button is pressed.
|
||||
*/
|
||||
virtual void buttonPressed (ButtonType buttonId, bool isDown) = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** Starts the device running and responding to events.
|
||||
|
||||
Returns true if it managed to open the device.
|
||||
|
||||
@param inExclusiveMode if true, the remote will be grabbed exclusively for this app,
|
||||
and will not be available to any other part of the system. If
|
||||
false, it will be shared with other apps.
|
||||
@see stop
|
||||
*/
|
||||
bool start (bool inExclusiveMode);
|
||||
|
||||
/** Stops the device running.
|
||||
@see start
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/** Returns true if the device has been started successfully.
|
||||
*/
|
||||
bool isActive() const;
|
||||
|
||||
/** Returns the ID number of the remote, if it has sent one.
|
||||
*/
|
||||
int getRemoteId() const { return remoteId; }
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void handleCallbackInternal();
|
||||
|
||||
private:
|
||||
void* device;
|
||||
void* queue;
|
||||
int remoteId;
|
||||
|
||||
bool open (bool openInExclusiveMode);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AppleRemoteDevice)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
BubbleMessageComponent::BubbleMessageComponent (int fadeOutLengthMs)
|
||||
: fadeOutLength (fadeOutLengthMs), mouseClickCounter (0),
|
||||
expiryTime (0), deleteAfterUse (false)
|
||||
{
|
||||
}
|
||||
|
||||
BubbleMessageComponent::~BubbleMessageComponent()
|
||||
{
|
||||
}
|
||||
|
||||
void BubbleMessageComponent::showAt (const Rectangle<int>& pos,
|
||||
const AttributedString& text,
|
||||
const int numMillisecondsBeforeRemoving,
|
||||
const bool removeWhenMouseClicked,
|
||||
const bool deleteSelfAfterUse)
|
||||
{
|
||||
createLayout (text);
|
||||
setPosition (pos);
|
||||
init (numMillisecondsBeforeRemoving, removeWhenMouseClicked, deleteSelfAfterUse);
|
||||
}
|
||||
|
||||
void BubbleMessageComponent::showAt (Component* const component,
|
||||
const AttributedString& text,
|
||||
const int numMillisecondsBeforeRemoving,
|
||||
const bool removeWhenMouseClicked,
|
||||
const bool deleteSelfAfterUse)
|
||||
{
|
||||
createLayout (text);
|
||||
setPosition (component);
|
||||
init (numMillisecondsBeforeRemoving, removeWhenMouseClicked, deleteSelfAfterUse);
|
||||
}
|
||||
|
||||
void BubbleMessageComponent::createLayout (const AttributedString& text)
|
||||
{
|
||||
textLayout.createLayoutWithBalancedLineLengths (text, 256);
|
||||
}
|
||||
|
||||
void BubbleMessageComponent::init (const int numMillisecondsBeforeRemoving,
|
||||
const bool removeWhenMouseClicked,
|
||||
const bool deleteSelfAfterUse)
|
||||
{
|
||||
setAlpha (1.0f);
|
||||
setVisible (true);
|
||||
deleteAfterUse = deleteSelfAfterUse;
|
||||
|
||||
expiryTime = numMillisecondsBeforeRemoving > 0
|
||||
? (Time::getMillisecondCounter() + (uint32) numMillisecondsBeforeRemoving) : 0;
|
||||
|
||||
mouseClickCounter = Desktop::getInstance().getMouseButtonClickCounter();
|
||||
|
||||
if (! (removeWhenMouseClicked && isShowing()))
|
||||
mouseClickCounter += 0xfffff;
|
||||
|
||||
startTimer (77);
|
||||
repaint();
|
||||
}
|
||||
|
||||
const float bubblePaddingX = 20.0f;
|
||||
const float bubblePaddingY = 14.0f;
|
||||
|
||||
void BubbleMessageComponent::getContentSize (int& w, int& h)
|
||||
{
|
||||
w = (int) (bubblePaddingX + textLayout.getWidth());
|
||||
h = (int) (bubblePaddingY + textLayout.getHeight());
|
||||
}
|
||||
|
||||
void BubbleMessageComponent::paintContent (Graphics& g, int w, int h)
|
||||
{
|
||||
g.setColour (findColour (TooltipWindow::textColourId));
|
||||
|
||||
textLayout.draw (g, Rectangle<float> (bubblePaddingX / 2.0f, bubblePaddingY / 2.0f,
|
||||
(float) w - bubblePaddingX, (float) h - bubblePaddingY));
|
||||
}
|
||||
|
||||
void BubbleMessageComponent::timerCallback()
|
||||
{
|
||||
if (Desktop::getInstance().getMouseButtonClickCounter() > mouseClickCounter)
|
||||
hide (false);
|
||||
else if (expiryTime != 0 && Time::getMillisecondCounter() > expiryTime)
|
||||
hide (true);
|
||||
}
|
||||
|
||||
void BubbleMessageComponent::hide (const bool fadeOut)
|
||||
{
|
||||
stopTimer();
|
||||
|
||||
if (fadeOut)
|
||||
Desktop::getInstance().getAnimator().fadeOut (this, fadeOutLength);
|
||||
else
|
||||
setVisible (false);
|
||||
|
||||
if (deleteAfterUse)
|
||||
delete this;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A speech-bubble component that displays a short message.
|
||||
|
||||
This can be used to show a message with the tail of the speech bubble
|
||||
pointing to a particular component or location on the screen.
|
||||
|
||||
@see BubbleComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API BubbleMessageComponent : public BubbleComponent,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a bubble component.
|
||||
|
||||
After creating one a BubbleComponent, do the following:
|
||||
- add it to an appropriate parent component, or put it on the
|
||||
desktop with Component::addToDesktop (0).
|
||||
- use the showAt() method to show a message.
|
||||
- it will make itself invisible after it times-out (and can optionally
|
||||
also delete itself), or you can reuse it somewhere else by calling
|
||||
showAt() again.
|
||||
*/
|
||||
BubbleMessageComponent (int fadeOutLengthMs = 150);
|
||||
|
||||
/** Destructor. */
|
||||
~BubbleMessageComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Shows a message bubble at a particular position.
|
||||
|
||||
This shows the bubble with its stem pointing to the given location
|
||||
(coordinates being relative to its parent component).
|
||||
|
||||
@param position the coords of the object to point to
|
||||
@param message the text to display
|
||||
@param numMillisecondsBeforeRemoving how long to leave it on the screen before removing itself
|
||||
from its parent component. If this is 0 or less, it
|
||||
will stay there until manually removed.
|
||||
@param removeWhenMouseClicked if this is true, the bubble will disappear as soon as a
|
||||
mouse button is pressed (anywhere on the screen)
|
||||
@param deleteSelfAfterUse if true, then the component will delete itself after
|
||||
it becomes invisible
|
||||
*/
|
||||
void showAt (const Rectangle<int>& position,
|
||||
const AttributedString& message,
|
||||
int numMillisecondsBeforeRemoving,
|
||||
bool removeWhenMouseClicked = true,
|
||||
bool deleteSelfAfterUse = false);
|
||||
|
||||
/** Shows a message bubble next to a particular component.
|
||||
|
||||
This shows the bubble with its stem pointing at the given component.
|
||||
|
||||
@param component the component that you want to point at
|
||||
@param message the text to display
|
||||
@param numMillisecondsBeforeRemoving how long to leave it on the screen before removing itself
|
||||
from its parent component. If this is 0 or less, it
|
||||
will stay there until manually removed.
|
||||
@param removeWhenMouseClicked if this is true, the bubble will disappear as soon as a
|
||||
mouse button is pressed (anywhere on the screen)
|
||||
@param deleteSelfAfterUse if true, then the component will delete itself after
|
||||
it becomes invisible
|
||||
*/
|
||||
void showAt (Component* component,
|
||||
const AttributedString& message,
|
||||
int numMillisecondsBeforeRemoving,
|
||||
bool removeWhenMouseClicked = true,
|
||||
bool deleteSelfAfterUse = false);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void getContentSize (int& w, int& h) override;
|
||||
/** @internal */
|
||||
void paintContent (Graphics& g, int w, int h) override;
|
||||
/** @internal */
|
||||
void timerCallback() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
int fadeOutLength, mouseClickCounter;
|
||||
TextLayout textLayout;
|
||||
int64 expiryTime;
|
||||
bool deleteAfterUse;
|
||||
|
||||
void createLayout (const AttributedString&);
|
||||
void init (int, bool, bool);
|
||||
void hide (bool fadeOut);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BubbleMessageComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,656 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct ColourComponentSlider final : public Slider
|
||||
{
|
||||
ColourComponentSlider (const String& name) : Slider (name)
|
||||
{
|
||||
setRange (0.0, 255.0, 1.0);
|
||||
}
|
||||
|
||||
String getTextFromValue (double value) override
|
||||
{
|
||||
return String::toHexString ((int) value).toUpperCase().paddedLeft ('0', 2);
|
||||
}
|
||||
|
||||
double getValueFromText (const String& text) override
|
||||
{
|
||||
return (double) text.getHexValue32();
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class ColourSelector::ColourSpaceView final : public Component
|
||||
{
|
||||
public:
|
||||
ColourSpaceView (ColourSelector& cs, float& hue, float& sat, float& val, int edgeSize)
|
||||
: owner (cs), h (hue), s (sat), v (val), edge (edgeSize)
|
||||
{
|
||||
addAndMakeVisible (marker);
|
||||
setMouseCursor (MouseCursor::CrosshairCursor);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
if (colours.isNull())
|
||||
{
|
||||
auto width = getWidth() / 2;
|
||||
auto height = getHeight() / 2;
|
||||
colours = Image (Image::RGB, width, height, false);
|
||||
|
||||
Image::BitmapData pixels (colours, Image::BitmapData::writeOnly);
|
||||
|
||||
for (int y = 0; y < height; ++y)
|
||||
{
|
||||
auto val = 1.0f - (float) y / (float) height;
|
||||
|
||||
for (int x = 0; x < width; ++x)
|
||||
{
|
||||
auto sat = (float) x / (float) width;
|
||||
pixels.setPixelColour (x, y, Colour (h, sat, val, 1.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g.setOpacity (1.0f);
|
||||
g.drawImageTransformed (colours,
|
||||
RectanglePlacement (RectanglePlacement::stretchToFit)
|
||||
.getTransformToFit (colours.getBounds().toFloat(),
|
||||
getLocalBounds().reduced (edge).toFloat()),
|
||||
false);
|
||||
}
|
||||
|
||||
void mouseDown (const MouseEvent& e) override
|
||||
{
|
||||
mouseDrag (e);
|
||||
}
|
||||
|
||||
void mouseDrag (const MouseEvent& e) override
|
||||
{
|
||||
auto sat = (float) (e.x - edge) / (float) (getWidth() - edge * 2);
|
||||
auto val = 1.0f - (float) (e.y - edge) / (float) (getHeight() - edge * 2);
|
||||
|
||||
owner.setSV (sat, val);
|
||||
}
|
||||
|
||||
void updateIfNeeded()
|
||||
{
|
||||
if (! approximatelyEqual (lastHue, h))
|
||||
{
|
||||
lastHue = h;
|
||||
colours = {};
|
||||
repaint();
|
||||
}
|
||||
|
||||
updateMarker();
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
colours = {};
|
||||
updateMarker();
|
||||
}
|
||||
|
||||
private:
|
||||
ColourSelector& owner;
|
||||
float& h;
|
||||
float& s;
|
||||
float& v;
|
||||
float lastHue = 0;
|
||||
const int edge;
|
||||
Image colours;
|
||||
|
||||
struct ColourSpaceMarker final : public Component
|
||||
{
|
||||
ColourSpaceMarker()
|
||||
{
|
||||
setInterceptsMouseClicks (false, false);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.setColour (Colour::greyLevel (0.1f));
|
||||
g.drawEllipse (1.0f, 1.0f, (float) getWidth() - 2.0f, (float) getHeight() - 2.0f, 1.0f);
|
||||
g.setColour (Colour::greyLevel (0.9f));
|
||||
g.drawEllipse (2.0f, 2.0f, (float) getWidth() - 4.0f, (float) getHeight() - 4.0f, 1.0f);
|
||||
}
|
||||
};
|
||||
|
||||
ColourSpaceMarker marker;
|
||||
|
||||
void updateMarker()
|
||||
{
|
||||
auto markerSize = jmax (14, edge * 2);
|
||||
auto area = getLocalBounds().reduced (edge);
|
||||
|
||||
marker.setBounds (Rectangle<int> (markerSize, markerSize)
|
||||
.withCentre (area.getRelativePoint (s, 1.0f - v)));
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (ColourSpaceView)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class ColourSelector::HueSelectorComp final : public Component
|
||||
{
|
||||
public:
|
||||
HueSelectorComp (ColourSelector& cs, float& hue, int edgeSize)
|
||||
: owner (cs), h (hue), edge (edgeSize)
|
||||
{
|
||||
addAndMakeVisible (marker);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
ColourGradient cg;
|
||||
cg.isRadial = false;
|
||||
cg.point1.setXY (0.0f, (float) edge);
|
||||
cg.point2.setXY (0.0f, (float) getHeight());
|
||||
|
||||
for (float i = 0.0f; i <= 1.0f; i += 0.02f)
|
||||
cg.addColour (i, Colour (i, 1.0f, 1.0f, 1.0f));
|
||||
|
||||
g.setGradientFill (cg);
|
||||
g.fillRect (getLocalBounds().reduced (edge));
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto markerSize = jmax (14, edge * 2);
|
||||
auto area = getLocalBounds().reduced (edge);
|
||||
|
||||
marker.setBounds (Rectangle<int> (getWidth(), markerSize)
|
||||
.withCentre (area.getRelativePoint (0.5f, h)));
|
||||
}
|
||||
|
||||
void mouseDown (const MouseEvent& e) override
|
||||
{
|
||||
mouseDrag (e);
|
||||
}
|
||||
|
||||
void mouseDrag (const MouseEvent& e) override
|
||||
{
|
||||
owner.setHue ((float) (e.y - edge) / (float) (getHeight() - edge * 2));
|
||||
}
|
||||
|
||||
void updateIfNeeded()
|
||||
{
|
||||
resized();
|
||||
}
|
||||
|
||||
private:
|
||||
ColourSelector& owner;
|
||||
float& h;
|
||||
const int edge;
|
||||
|
||||
struct HueSelectorMarker final : public Component
|
||||
{
|
||||
HueSelectorMarker()
|
||||
{
|
||||
setInterceptsMouseClicks (false, false);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
auto cw = (float) getWidth();
|
||||
auto ch = (float) getHeight();
|
||||
|
||||
Path p;
|
||||
p.addTriangle (1.0f, 1.0f,
|
||||
cw * 0.3f, ch * 0.5f,
|
||||
1.0f, ch - 1.0f);
|
||||
|
||||
p.addTriangle (cw - 1.0f, 1.0f,
|
||||
cw * 0.7f, ch * 0.5f,
|
||||
cw - 1.0f, ch - 1.0f);
|
||||
|
||||
g.setColour (Colours::white.withAlpha (0.75f));
|
||||
g.fillPath (p);
|
||||
|
||||
g.setColour (Colours::black.withAlpha (0.75f));
|
||||
g.strokePath (p, PathStrokeType (1.2f));
|
||||
}
|
||||
};
|
||||
|
||||
HueSelectorMarker marker;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (HueSelectorComp)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class ColourSelector::SwatchComponent final : public Component
|
||||
{
|
||||
public:
|
||||
SwatchComponent (ColourSelector& cs, int itemIndex)
|
||||
: owner (cs), index (itemIndex)
|
||||
{
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
auto col = owner.getSwatchColour (index);
|
||||
|
||||
g.fillCheckerBoard (getLocalBounds().toFloat(), 6.0f, 6.0f,
|
||||
Colour (0xffdddddd).overlaidWith (col),
|
||||
Colour (0xffffffff).overlaidWith (col));
|
||||
}
|
||||
|
||||
void mouseDown (const MouseEvent&) override
|
||||
{
|
||||
PopupMenu m;
|
||||
m.addItem (1, TRANS ("Use this swatch as the current colour"));
|
||||
m.addSeparator();
|
||||
m.addItem (2, TRANS ("Set this swatch to the current colour"));
|
||||
|
||||
m.showMenuAsync (PopupMenu::Options().withTargetComponent (this),
|
||||
ModalCallbackFunction::forComponent (menuStaticCallback, this));
|
||||
}
|
||||
|
||||
private:
|
||||
ColourSelector& owner;
|
||||
const int index;
|
||||
|
||||
static void menuStaticCallback (int result, SwatchComponent* comp)
|
||||
{
|
||||
if (comp != nullptr)
|
||||
{
|
||||
if (result == 1) comp->setColourFromSwatch();
|
||||
if (result == 2) comp->setSwatchFromColour();
|
||||
}
|
||||
}
|
||||
|
||||
void setColourFromSwatch()
|
||||
{
|
||||
owner.setCurrentColour (owner.getSwatchColour (index));
|
||||
}
|
||||
|
||||
void setSwatchFromColour()
|
||||
{
|
||||
if (owner.getSwatchColour (index) != owner.getCurrentColour())
|
||||
{
|
||||
owner.setSwatchColour (index, owner.getCurrentColour());
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (SwatchComponent)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class ColourSelector::ColourPreviewComp final : public Component
|
||||
{
|
||||
public:
|
||||
ColourPreviewComp (ColourSelector& cs, bool isEditable)
|
||||
: owner (cs)
|
||||
{
|
||||
colourLabel.setFont (labelFont);
|
||||
colourLabel.setJustificationType (Justification::centred);
|
||||
|
||||
if (isEditable)
|
||||
{
|
||||
colourLabel.setEditable (true);
|
||||
|
||||
colourLabel.onEditorShow = [this]
|
||||
{
|
||||
if (auto* ed = colourLabel.getCurrentTextEditor())
|
||||
ed->setInputRestrictions ((owner.flags & showAlphaChannel) ? 8 : 6, "1234567890ABCDEFabcdef");
|
||||
};
|
||||
|
||||
colourLabel.onEditorHide = [this]
|
||||
{
|
||||
updateColourIfNecessary (colourLabel.getText());
|
||||
};
|
||||
}
|
||||
|
||||
addAndMakeVisible (colourLabel);
|
||||
}
|
||||
|
||||
void updateIfNeeded()
|
||||
{
|
||||
auto newColour = owner.getCurrentColour();
|
||||
|
||||
if (currentColour != newColour)
|
||||
{
|
||||
currentColour = newColour;
|
||||
auto textColour = (Colours::white.overlaidWith (currentColour).contrasting());
|
||||
|
||||
colourLabel.setColour (Label::textColourId, textColour);
|
||||
colourLabel.setColour (Label::textWhenEditingColourId, textColour);
|
||||
colourLabel.setText (currentColour.toDisplayString ((owner.flags & showAlphaChannel) != 0), dontSendNotification);
|
||||
|
||||
labelWidth = GlyphArrangement::getStringWidthInt (labelFont, colourLabel.getText());
|
||||
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillCheckerBoard (getLocalBounds().toFloat(), 10.0f, 10.0f,
|
||||
Colour (0xffdddddd).overlaidWith (currentColour),
|
||||
Colour (0xffffffff).overlaidWith (currentColour));
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
colourLabel.centreWithSize (labelWidth + 10, (int) labelFont.getHeight() + 10);
|
||||
}
|
||||
|
||||
private:
|
||||
void updateColourIfNecessary (const String& newColourString)
|
||||
{
|
||||
auto newColour = Colour::fromString (newColourString);
|
||||
|
||||
if (newColour != currentColour)
|
||||
owner.setCurrentColour (newColour);
|
||||
}
|
||||
|
||||
ColourSelector& owner;
|
||||
|
||||
Colour currentColour;
|
||||
Font labelFont { withDefaultMetrics (FontOptions { 14.0f, Font::bold }) };
|
||||
int labelWidth = 0;
|
||||
Label colourLabel;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ColourPreviewComp)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ColourSelector::ColourSelector (int sectionsToShow, int edge, int gapAroundColourSpaceComponent)
|
||||
: colour (Colours::white),
|
||||
flags (sectionsToShow),
|
||||
edgeGap (edge)
|
||||
{
|
||||
// not much point having a selector with no components in it!
|
||||
jassert ((flags & (showColourAtTop | showSliders | showColourspace)) != 0);
|
||||
|
||||
updateHSV();
|
||||
|
||||
if ((flags & showColourAtTop) != 0)
|
||||
{
|
||||
previewComponent.reset (new ColourPreviewComp (*this, (flags & editableColour) != 0));
|
||||
addAndMakeVisible (previewComponent.get());
|
||||
}
|
||||
|
||||
if ((flags & showSliders) != 0)
|
||||
{
|
||||
sliders[0].reset (new ColourComponentSlider (TRANS ("red")));
|
||||
sliders[1].reset (new ColourComponentSlider (TRANS ("green")));
|
||||
sliders[2].reset (new ColourComponentSlider (TRANS ("blue")));
|
||||
sliders[3].reset (new ColourComponentSlider (TRANS ("alpha")));
|
||||
|
||||
addAndMakeVisible (sliders[0].get());
|
||||
addAndMakeVisible (sliders[1].get());
|
||||
addAndMakeVisible (sliders[2].get());
|
||||
addChildComponent (sliders[3].get());
|
||||
|
||||
sliders[3]->setVisible ((flags & showAlphaChannel) != 0);
|
||||
|
||||
for (auto& slider : sliders)
|
||||
slider->onValueChange = [this] { changeColour(); };
|
||||
}
|
||||
|
||||
if ((flags & showColourspace) != 0)
|
||||
{
|
||||
colourSpace.reset (new ColourSpaceView (*this, h, s, v, gapAroundColourSpaceComponent));
|
||||
hueSelector.reset (new HueSelectorComp (*this, h, gapAroundColourSpaceComponent));
|
||||
|
||||
addAndMakeVisible (colourSpace.get());
|
||||
addAndMakeVisible (hueSelector.get());
|
||||
}
|
||||
|
||||
update (dontSendNotification);
|
||||
}
|
||||
|
||||
ColourSelector::~ColourSelector()
|
||||
{
|
||||
dispatchPendingMessages();
|
||||
swatchComponents.clear();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Colour ColourSelector::getCurrentColour() const
|
||||
{
|
||||
return ((flags & showAlphaChannel) != 0) ? colour : colour.withAlpha ((uint8) 0xff);
|
||||
}
|
||||
|
||||
void ColourSelector::setCurrentColour (Colour c, NotificationType notification)
|
||||
{
|
||||
if (c != colour)
|
||||
{
|
||||
colour = ((flags & showAlphaChannel) != 0) ? c : c.withAlpha ((uint8) 0xff);
|
||||
|
||||
updateHSV();
|
||||
update (notification);
|
||||
}
|
||||
}
|
||||
|
||||
void ColourSelector::setHue (float newH)
|
||||
{
|
||||
newH = jlimit (0.0f, 1.0f, newH);
|
||||
|
||||
if (! approximatelyEqual (h, newH))
|
||||
{
|
||||
h = newH;
|
||||
colour = Colour (h, s, v, colour.getFloatAlpha());
|
||||
update (sendNotification);
|
||||
}
|
||||
}
|
||||
|
||||
void ColourSelector::setSV (float newS, float newV)
|
||||
{
|
||||
newS = jlimit (0.0f, 1.0f, newS);
|
||||
newV = jlimit (0.0f, 1.0f, newV);
|
||||
|
||||
if (! approximatelyEqual (s, newS) || ! approximatelyEqual (v, newV))
|
||||
{
|
||||
s = newS;
|
||||
v = newV;
|
||||
colour = Colour (h, s, v, colour.getFloatAlpha());
|
||||
update (sendNotification);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ColourSelector::updateHSV()
|
||||
{
|
||||
colour.getHSB (h, s, v);
|
||||
}
|
||||
|
||||
void ColourSelector::update (NotificationType notification)
|
||||
{
|
||||
if (sliders[0] != nullptr)
|
||||
{
|
||||
sliders[0]->setValue ((int) colour.getRed(), notification);
|
||||
sliders[1]->setValue ((int) colour.getGreen(), notification);
|
||||
sliders[2]->setValue ((int) colour.getBlue(), notification);
|
||||
sliders[3]->setValue ((int) colour.getAlpha(), notification);
|
||||
}
|
||||
|
||||
if (colourSpace != nullptr)
|
||||
{
|
||||
colourSpace->updateIfNeeded();
|
||||
hueSelector->updateIfNeeded();
|
||||
}
|
||||
|
||||
if (previewComponent != nullptr)
|
||||
previewComponent->updateIfNeeded();
|
||||
|
||||
if (notification != dontSendNotification)
|
||||
sendChangeMessage();
|
||||
|
||||
if (notification == sendNotificationSync)
|
||||
dispatchPendingMessages();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ColourSelector::paint (Graphics& g)
|
||||
{
|
||||
g.fillAll (findColour (backgroundColourId));
|
||||
|
||||
if ((flags & showSliders) != 0)
|
||||
{
|
||||
g.setColour (findColour (labelTextColourId));
|
||||
g.setFont (11.0f);
|
||||
|
||||
for (auto& slider : sliders)
|
||||
{
|
||||
if (slider->isVisible())
|
||||
g.drawText (slider->getName() + ":",
|
||||
0, slider->getY(),
|
||||
slider->getX() - 8, slider->getHeight(),
|
||||
Justification::centredRight, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ColourSelector::resized()
|
||||
{
|
||||
const int swatchesPerRow = 8;
|
||||
const int swatchHeight = 22;
|
||||
|
||||
const int numSliders = ((flags & showAlphaChannel) != 0) ? 4 : 3;
|
||||
const int numSwatches = getNumSwatches();
|
||||
|
||||
const int swatchSpace = numSwatches > 0 ? edgeGap + swatchHeight * ((numSwatches + 7) / swatchesPerRow) : 0;
|
||||
const int sliderSpace = ((flags & showSliders) != 0) ? jmin (22 * numSliders + edgeGap, proportionOfHeight (0.3f)) : 0;
|
||||
const int topSpace = ((flags & showColourAtTop) != 0) ? jmin (30 + edgeGap * 2, proportionOfHeight (0.2f)) : edgeGap;
|
||||
|
||||
if (previewComponent != nullptr)
|
||||
previewComponent->setBounds (edgeGap, edgeGap, getWidth() - edgeGap * 2, topSpace - edgeGap * 2);
|
||||
|
||||
int y = topSpace;
|
||||
|
||||
if ((flags & showColourspace) != 0)
|
||||
{
|
||||
const int hueWidth = jmin (50, proportionOfWidth (0.15f));
|
||||
|
||||
colourSpace->setBounds (edgeGap, y,
|
||||
getWidth() - hueWidth - edgeGap - 4,
|
||||
getHeight() - topSpace - sliderSpace - swatchSpace - edgeGap);
|
||||
|
||||
hueSelector->setBounds (colourSpace->getRight() + 4, y,
|
||||
getWidth() - edgeGap - (colourSpace->getRight() + 4),
|
||||
colourSpace->getHeight());
|
||||
|
||||
y = getHeight() - sliderSpace - swatchSpace - edgeGap;
|
||||
}
|
||||
|
||||
if ((flags & showSliders) != 0)
|
||||
{
|
||||
auto sliderHeight = jmax (4, sliderSpace / numSliders);
|
||||
|
||||
for (int i = 0; i < numSliders; ++i)
|
||||
{
|
||||
sliders[i]->setBounds (proportionOfWidth (0.2f), y,
|
||||
proportionOfWidth (0.72f), sliderHeight - 2);
|
||||
|
||||
y += sliderHeight;
|
||||
}
|
||||
}
|
||||
|
||||
if (numSwatches > 0)
|
||||
{
|
||||
const int startX = 8;
|
||||
const int xGap = 4;
|
||||
const int yGap = 4;
|
||||
const int swatchWidth = (getWidth() - startX * 2) / swatchesPerRow;
|
||||
y += edgeGap;
|
||||
|
||||
if (swatchComponents.size() != numSwatches)
|
||||
{
|
||||
swatchComponents.clear();
|
||||
|
||||
for (int i = 0; i < numSwatches; ++i)
|
||||
{
|
||||
auto* sc = new SwatchComponent (*this, i);
|
||||
swatchComponents.add (sc);
|
||||
addAndMakeVisible (sc);
|
||||
}
|
||||
}
|
||||
|
||||
int x = startX;
|
||||
|
||||
for (int i = 0; i < swatchComponents.size(); ++i)
|
||||
{
|
||||
auto* sc = swatchComponents.getUnchecked (i);
|
||||
|
||||
sc->setBounds (x + xGap / 2,
|
||||
y + yGap / 2,
|
||||
swatchWidth - xGap,
|
||||
swatchHeight - yGap);
|
||||
|
||||
if (((i + 1) % swatchesPerRow) == 0)
|
||||
{
|
||||
x = startX;
|
||||
y += swatchHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
x += swatchWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ColourSelector::changeColour()
|
||||
{
|
||||
if (sliders[0] != nullptr)
|
||||
setCurrentColour (Colour ((uint8) sliders[0]->getValue(),
|
||||
(uint8) sliders[1]->getValue(),
|
||||
(uint8) sliders[2]->getValue(),
|
||||
(uint8) sliders[3]->getValue()));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int ColourSelector::getNumSwatches() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Colour ColourSelector::getSwatchColour (int) const
|
||||
{
|
||||
jassertfalse; // if you've overridden getNumSwatches(), you also need to implement this method
|
||||
return Colours::black;
|
||||
}
|
||||
|
||||
void ColourSelector::setSwatchColour (int, const Colour&)
|
||||
{
|
||||
jassertfalse; // if you've overridden getNumSwatches(), you also need to implement this method
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that lets the user choose a colour.
|
||||
|
||||
This shows RGB sliders and a colourspace that the user can pick colours from.
|
||||
|
||||
This class is also a ChangeBroadcaster, so listeners can register to be told
|
||||
when the colour changes.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ColourSelector : public Component,
|
||||
public ChangeBroadcaster
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Options for the type of selector to show. These are passed into the constructor. */
|
||||
enum ColourSelectorOptions
|
||||
{
|
||||
showAlphaChannel = 1 << 0, /**< if set, the colour's alpha channel can be changed as well as its RGB. */
|
||||
|
||||
showColourAtTop = 1 << 1, /**< if set, a swatch of the colour is shown at the top of the component. */
|
||||
editableColour = 1 << 2, /**< if set, the colour shows at the top of the component is editable. */
|
||||
showSliders = 1 << 3, /**< if set, RGB sliders are shown at the bottom of the component. */
|
||||
showColourspace = 1 << 4 /**< if set, a big HSV selector is shown. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a ColourSelector object.
|
||||
|
||||
The flags are a combination of values from the ColourSelectorOptions enum, specifying
|
||||
which of the selector's features should be visible.
|
||||
|
||||
The edgeGap value specifies the amount of space to leave around the edge.
|
||||
|
||||
gapAroundColourSpaceComponent indicates how much of a gap to put around the
|
||||
colourspace and hue selector components.
|
||||
*/
|
||||
ColourSelector (int flags = (showAlphaChannel | showColourAtTop | showSliders | showColourspace),
|
||||
int edgeGap = 4,
|
||||
int gapAroundColourSpaceComponent = 7);
|
||||
|
||||
/** Destructor. */
|
||||
~ColourSelector() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the colour that the user has currently selected.
|
||||
|
||||
The ColourSelector class is also a ChangeBroadcaster, so listeners can
|
||||
register to be told when the colour changes.
|
||||
|
||||
@see setCurrentColour
|
||||
*/
|
||||
Colour getCurrentColour() const;
|
||||
|
||||
/** Changes the colour that is currently being shown.
|
||||
|
||||
@param newColour the new colour to show
|
||||
@param notificationType whether to send a notification of the change to listeners.
|
||||
A notification will only be sent if the colour has changed.
|
||||
*/
|
||||
void setCurrentColour (Colour newColour, NotificationType notificationType = sendNotification);
|
||||
|
||||
//==============================================================================
|
||||
/** Tells the selector how many preset colour swatches you want to have on the component.
|
||||
|
||||
To enable swatches, you'll need to override getNumSwatches(), getSwatchColour(), and
|
||||
setSwatchColour(), to return the number of colours you want, and to set and retrieve
|
||||
their values.
|
||||
*/
|
||||
virtual int getNumSwatches() const;
|
||||
|
||||
/** Called by the selector to find out the colour of one of the swatches.
|
||||
|
||||
Your subclass should return the colour of the swatch with the given index.
|
||||
|
||||
To enable swatches, you'll need to override getNumSwatches(), getSwatchColour(), and
|
||||
setSwatchColour(), to return the number of colours you want, and to set and retrieve
|
||||
their values.
|
||||
*/
|
||||
virtual Colour getSwatchColour (int index) const;
|
||||
|
||||
/** Called by the selector when the user puts a new colour into one of the swatches.
|
||||
|
||||
Your subclass should change the colour of the swatch with the given index.
|
||||
|
||||
To enable swatches, you'll need to override getNumSwatches(), getSwatchColour(), and
|
||||
setSwatchColour(), to return the number of colours you want, and to set and retrieve
|
||||
their values.
|
||||
*/
|
||||
virtual void setSwatchColour (int index, const Colour& newColour);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the keyboard.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1007000, /**< the colour used to fill the component's background. */
|
||||
labelTextColourId = 0x1007001 /**< the colour used for the labels next to the sliders. */
|
||||
};
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class SwatchComponent;
|
||||
class ColourSpaceView;
|
||||
class HueSelectorComp;
|
||||
class ColourPreviewComp;
|
||||
|
||||
Colour colour;
|
||||
float h, s, v;
|
||||
std::unique_ptr<Slider> sliders[4];
|
||||
std::unique_ptr<ColourSpaceView> colourSpace;
|
||||
std::unique_ptr<HueSelectorComp> hueSelector;
|
||||
std::unique_ptr<ColourPreviewComp> previewComponent;
|
||||
OwnedArray<SwatchComponent> swatchComponents;
|
||||
const int flags;
|
||||
int edgeGap;
|
||||
|
||||
void setHue (float newH);
|
||||
void setSV (float newS, float newV);
|
||||
void updateHSV();
|
||||
void update (NotificationType);
|
||||
void changeColour();
|
||||
void paint (Graphics&) override;
|
||||
void resized() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ColourSelector)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,491 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class KeyMappingEditorComponent::ChangeKeyButton final : public Button
|
||||
{
|
||||
public:
|
||||
ChangeKeyButton (KeyMappingEditorComponent& kec, CommandID command,
|
||||
const String& keyName, int keyIndex)
|
||||
: Button (keyName),
|
||||
owner (kec),
|
||||
commandID (command),
|
||||
keyNum (keyIndex)
|
||||
{
|
||||
setWantsKeyboardFocus (false);
|
||||
setTriggeredOnMouseDown (keyNum >= 0);
|
||||
|
||||
setTooltip (keyIndex < 0 ? TRANS ("Adds a new key-mapping")
|
||||
: TRANS ("Click to change this key-mapping"));
|
||||
}
|
||||
|
||||
void paintButton (Graphics& g, bool /*isOver*/, bool /*isDown*/) override
|
||||
{
|
||||
getLookAndFeel().drawKeymapChangeButton (g, getWidth(), getHeight(), *this,
|
||||
keyNum >= 0 ? getName() : String());
|
||||
}
|
||||
|
||||
void clicked() override
|
||||
{
|
||||
if (keyNum >= 0)
|
||||
{
|
||||
Component::SafePointer<ChangeKeyButton> button (this);
|
||||
PopupMenu m;
|
||||
|
||||
m.addItem (TRANS ("Change this key-mapping"),
|
||||
[button]
|
||||
{
|
||||
if (button != nullptr)
|
||||
button.getComponent()->assignNewKey();
|
||||
});
|
||||
|
||||
m.addSeparator();
|
||||
|
||||
m.addItem (TRANS ("Remove this key-mapping"),
|
||||
[button]
|
||||
{
|
||||
if (button != nullptr)
|
||||
button->owner.getMappings().removeKeyPress (button->commandID,
|
||||
button->keyNum);
|
||||
});
|
||||
|
||||
m.showMenuAsync (PopupMenu::Options().withTargetComponent (this));
|
||||
}
|
||||
else
|
||||
{
|
||||
assignNewKey(); // + button pressed..
|
||||
}
|
||||
}
|
||||
|
||||
using Button::clicked;
|
||||
|
||||
void fitToContent (const int h) noexcept
|
||||
{
|
||||
if (keyNum < 0)
|
||||
{
|
||||
setSize (h, h);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto idealWidth = GlyphArrangement::getStringWidthInt (withDefaultMetrics (FontOptions { (float) h * 0.6f }), getName());
|
||||
setSize (jlimit (h * 4, h * 8, 6 + idealWidth), h);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class KeyEntryWindow final : public AlertWindow
|
||||
{
|
||||
public:
|
||||
KeyEntryWindow (KeyMappingEditorComponent& kec)
|
||||
: AlertWindow (TRANS ("New key-mapping"),
|
||||
TRANS ("Please press a key combination now..."),
|
||||
MessageBoxIconType::NoIcon),
|
||||
owner (kec)
|
||||
{
|
||||
addButton (TRANS ("OK"), 1);
|
||||
addButton (TRANS ("Cancel"), 0);
|
||||
|
||||
// (avoid return + escape keys getting processed by the buttons..)
|
||||
for (auto* child : getChildren())
|
||||
child->setWantsKeyboardFocus (false);
|
||||
|
||||
setWantsKeyboardFocus (true);
|
||||
grabKeyboardFocus();
|
||||
}
|
||||
|
||||
bool keyPressed (const KeyPress& key) override
|
||||
{
|
||||
lastPress = key;
|
||||
String message (TRANS ("Key") + ": " + owner.getDescriptionForKeyPress (key));
|
||||
|
||||
auto previousCommand = owner.getMappings().findCommandForKeyPress (key);
|
||||
|
||||
if (previousCommand != 0)
|
||||
message << "\n\n("
|
||||
<< TRANS ("Currently assigned to \"CMDN\"")
|
||||
.replace ("CMDN", TRANS (owner.getCommandManager().getNameOfCommand (previousCommand)))
|
||||
<< ')';
|
||||
|
||||
setMessage (message);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool keyStateChanged (bool) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
KeyPress lastPress;
|
||||
|
||||
private:
|
||||
KeyMappingEditorComponent& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (KeyEntryWindow)
|
||||
};
|
||||
|
||||
void setNewKey (const KeyPress& newKey, bool dontAskUser)
|
||||
{
|
||||
if (newKey.isValid())
|
||||
{
|
||||
auto previousCommand = owner.getMappings().findCommandForKeyPress (newKey);
|
||||
|
||||
if (previousCommand == 0 || dontAskUser)
|
||||
{
|
||||
owner.getMappings().removeKeyPress (newKey);
|
||||
|
||||
if (keyNum >= 0)
|
||||
owner.getMappings().removeKeyPress (commandID, keyNum);
|
||||
|
||||
owner.getMappings().addKeyPress (commandID, newKey, keyNum);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto options = MessageBoxOptions::makeOptionsOkCancel (MessageBoxIconType::WarningIcon,
|
||||
TRANS ("Change key-mapping"),
|
||||
TRANS ("This key is already assigned to the command \"CMDN\"")
|
||||
.replace ("CMDN", owner.getCommandManager().getNameOfCommand (previousCommand))
|
||||
+ "\n\n"
|
||||
+ TRANS ("Do you want to re-assign it to this new command instead?"),
|
||||
TRANS ("Re-assign"),
|
||||
TRANS ("Cancel"),
|
||||
this);
|
||||
messageBox = AlertWindow::showScopedAsync (options, [this, newKey] (int result)
|
||||
{
|
||||
if (result != 0)
|
||||
setNewKey (newKey, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void keyChosen (int result, ChangeKeyButton* button)
|
||||
{
|
||||
if (button != nullptr && button->currentKeyEntryWindow != nullptr)
|
||||
{
|
||||
if (result != 0)
|
||||
{
|
||||
button->currentKeyEntryWindow->setVisible (false);
|
||||
button->setNewKey (button->currentKeyEntryWindow->lastPress, false);
|
||||
}
|
||||
|
||||
button->currentKeyEntryWindow.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void assignNewKey()
|
||||
{
|
||||
currentKeyEntryWindow.reset (new KeyEntryWindow (owner));
|
||||
currentKeyEntryWindow->enterModalState (true, ModalCallbackFunction::forComponent (keyChosen, this));
|
||||
}
|
||||
|
||||
private:
|
||||
KeyMappingEditorComponent& owner;
|
||||
const CommandID commandID;
|
||||
const int keyNum;
|
||||
std::unique_ptr<KeyEntryWindow> currentKeyEntryWindow;
|
||||
ScopedMessageBox messageBox;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChangeKeyButton)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class KeyMappingEditorComponent::ItemComponent final : public Component
|
||||
{
|
||||
public:
|
||||
ItemComponent (KeyMappingEditorComponent& kec, CommandID command)
|
||||
: owner (kec), commandID (command)
|
||||
{
|
||||
setInterceptsMouseClicks (false, true);
|
||||
|
||||
const bool isReadOnly = owner.isCommandReadOnly (commandID);
|
||||
|
||||
auto keyPresses = owner.getMappings().getKeyPressesAssignedToCommand (commandID);
|
||||
|
||||
for (int i = 0; i < jmin ((int) maxNumAssignments, keyPresses.size()); ++i)
|
||||
addKeyPressButton (owner.getDescriptionForKeyPress (keyPresses.getReference (i)), i, isReadOnly);
|
||||
|
||||
addKeyPressButton ("Change Key Mapping", -1, isReadOnly);
|
||||
}
|
||||
|
||||
void addKeyPressButton (const String& desc, const int index, const bool isReadOnly)
|
||||
{
|
||||
auto* b = new ChangeKeyButton (owner, commandID, desc, index);
|
||||
keyChangeButtons.add (b);
|
||||
|
||||
b->setEnabled (! isReadOnly);
|
||||
b->setVisible (keyChangeButtons.size() <= (int) maxNumAssignments);
|
||||
addChildComponent (b);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.setFont ((float) getHeight() * 0.7f);
|
||||
g.setColour (owner.findColour (KeyMappingEditorComponent::textColourId));
|
||||
|
||||
g.drawFittedText (TRANS (owner.getCommandManager().getNameOfCommand (commandID)),
|
||||
4, 0, jmax (40, getChildComponent (0)->getX() - 5), getHeight(),
|
||||
Justification::centredLeft, true);
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
int x = getWidth() - 4;
|
||||
|
||||
for (int i = keyChangeButtons.size(); --i >= 0;)
|
||||
{
|
||||
auto* b = keyChangeButtons.getUnchecked (i);
|
||||
|
||||
b->fitToContent (getHeight() - 2);
|
||||
b->setTopRightPosition (x, 1);
|
||||
x = b->getX() - 5;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
|
||||
{
|
||||
return createIgnoredAccessibilityHandler (*this);
|
||||
}
|
||||
|
||||
KeyMappingEditorComponent& owner;
|
||||
OwnedArray<ChangeKeyButton> keyChangeButtons;
|
||||
const CommandID commandID;
|
||||
|
||||
enum { maxNumAssignments = 3 };
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemComponent)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class KeyMappingEditorComponent::MappingItem final : public TreeViewItem
|
||||
{
|
||||
public:
|
||||
MappingItem (KeyMappingEditorComponent& kec, CommandID command)
|
||||
: owner (kec), commandID (command)
|
||||
{}
|
||||
|
||||
String getUniqueName() const override { return String ((int) commandID) + "_id"; }
|
||||
bool mightContainSubItems() override { return false; }
|
||||
int getItemHeight() const override { return 20; }
|
||||
std::unique_ptr<Component> createItemComponent() override { return std::make_unique<ItemComponent> (owner, commandID); }
|
||||
String getAccessibilityName() override { return TRANS (owner.getCommandManager().getNameOfCommand (commandID)); }
|
||||
|
||||
private:
|
||||
KeyMappingEditorComponent& owner;
|
||||
const CommandID commandID;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MappingItem)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
class KeyMappingEditorComponent::CategoryItem final : public TreeViewItem
|
||||
{
|
||||
public:
|
||||
CategoryItem (KeyMappingEditorComponent& kec, const String& name)
|
||||
: owner (kec), categoryName (name)
|
||||
{}
|
||||
|
||||
String getUniqueName() const override { return categoryName + "_cat"; }
|
||||
bool mightContainSubItems() override { return true; }
|
||||
int getItemHeight() const override { return 22; }
|
||||
String getAccessibilityName() override { return categoryName; }
|
||||
|
||||
void paintItem (Graphics& g, int width, int height) override
|
||||
{
|
||||
g.setFont (owner.withDefaultMetrics (FontOptions ((float) height * 0.7f, Font::bold)));
|
||||
g.setColour (owner.findColour (KeyMappingEditorComponent::textColourId));
|
||||
|
||||
g.drawText (TRANS (categoryName), 2, 0, width - 2, height, Justification::centredLeft, true);
|
||||
}
|
||||
|
||||
void itemOpennessChanged (bool isNowOpen) override
|
||||
{
|
||||
if (isNowOpen)
|
||||
{
|
||||
if (getNumSubItems() == 0)
|
||||
for (auto command : owner.getCommandManager().getCommandsInCategory (categoryName))
|
||||
if (owner.shouldCommandBeIncluded (command))
|
||||
addSubItem (new MappingItem (owner, command));
|
||||
}
|
||||
else
|
||||
{
|
||||
clearSubItems();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
KeyMappingEditorComponent& owner;
|
||||
String categoryName;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CategoryItem)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class KeyMappingEditorComponent::TopLevelItem final : public TreeViewItem,
|
||||
private ChangeListener
|
||||
{
|
||||
public:
|
||||
TopLevelItem (KeyMappingEditorComponent& kec) : owner (kec)
|
||||
{
|
||||
setLinesDrawnForSubItems (false);
|
||||
owner.getMappings().addChangeListener (this);
|
||||
}
|
||||
|
||||
~TopLevelItem() override
|
||||
{
|
||||
owner.getMappings().removeChangeListener (this);
|
||||
}
|
||||
|
||||
bool mightContainSubItems() override { return true; }
|
||||
String getUniqueName() const override { return "keys"; }
|
||||
|
||||
void changeListenerCallback (ChangeBroadcaster*) override
|
||||
{
|
||||
const OpennessRestorer opennessRestorer (*this);
|
||||
clearSubItems();
|
||||
|
||||
for (auto category : owner.getCommandManager().getCommandCategories())
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (auto command : owner.getCommandManager().getCommandsInCategory (category))
|
||||
if (owner.shouldCommandBeIncluded (command))
|
||||
++count;
|
||||
|
||||
if (count > 0)
|
||||
addSubItem (new CategoryItem (owner, category));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
KeyMappingEditorComponent& owner;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
KeyMappingEditorComponent::KeyMappingEditorComponent (KeyPressMappingSet& mappingManager,
|
||||
const bool showResetToDefaultButton)
|
||||
: mappings (mappingManager),
|
||||
resetButton (TRANS ("reset to defaults"))
|
||||
{
|
||||
treeItem.reset (new TopLevelItem (*this));
|
||||
|
||||
if (showResetToDefaultButton)
|
||||
{
|
||||
addAndMakeVisible (resetButton);
|
||||
|
||||
resetButton.onClick = [this]
|
||||
{
|
||||
auto options = MessageBoxOptions::makeOptionsOkCancel (MessageBoxIconType::QuestionIcon,
|
||||
TRANS ("Reset to defaults"),
|
||||
TRANS ("Are you sure you want to reset all the key-mappings to their default state?"),
|
||||
TRANS ("Reset"),
|
||||
{},
|
||||
this);
|
||||
messageBox = AlertWindow::showScopedAsync (options, [this] (int result)
|
||||
{
|
||||
if (result != 0)
|
||||
getMappings().resetToDefaultMappings();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
addAndMakeVisible (tree);
|
||||
tree.setTitle ("Key Mappings");
|
||||
tree.setColour (TreeView::backgroundColourId, findColour (backgroundColourId));
|
||||
tree.setRootItemVisible (false);
|
||||
tree.setDefaultOpenness (true);
|
||||
tree.setRootItem (treeItem.get());
|
||||
tree.setIndentSize (12);
|
||||
}
|
||||
|
||||
KeyMappingEditorComponent::~KeyMappingEditorComponent()
|
||||
{
|
||||
tree.setRootItem (nullptr);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void KeyMappingEditorComponent::setColours (Colour mainBackground,
|
||||
Colour textColour)
|
||||
{
|
||||
setColour (backgroundColourId, mainBackground);
|
||||
setColour (textColourId, textColour);
|
||||
tree.setColour (TreeView::backgroundColourId, mainBackground);
|
||||
}
|
||||
|
||||
void KeyMappingEditorComponent::parentHierarchyChanged()
|
||||
{
|
||||
treeItem->changeListenerCallback (nullptr);
|
||||
}
|
||||
|
||||
void KeyMappingEditorComponent::resized()
|
||||
{
|
||||
int h = getHeight();
|
||||
|
||||
if (resetButton.isVisible())
|
||||
{
|
||||
const int buttonHeight = 20;
|
||||
h -= buttonHeight + 8;
|
||||
int x = getWidth() - 8;
|
||||
|
||||
resetButton.changeWidthToFitText (buttonHeight);
|
||||
resetButton.setTopRightPosition (x, h + 6);
|
||||
}
|
||||
|
||||
tree.setBounds (0, 0, getWidth(), h);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool KeyMappingEditorComponent::shouldCommandBeIncluded (const CommandID commandID)
|
||||
{
|
||||
auto* ci = mappings.getCommandManager().getCommandForID (commandID);
|
||||
|
||||
return ci != nullptr && (ci->flags & ApplicationCommandInfo::hiddenFromKeyEditor) == 0;
|
||||
}
|
||||
|
||||
bool KeyMappingEditorComponent::isCommandReadOnly (const CommandID commandID)
|
||||
{
|
||||
auto* ci = mappings.getCommandManager().getCommandForID (commandID);
|
||||
|
||||
return ci != nullptr && (ci->flags & ApplicationCommandInfo::readOnlyInKeyEditor) != 0;
|
||||
}
|
||||
|
||||
String KeyMappingEditorComponent::getDescriptionForKeyPress (const KeyPress& key)
|
||||
{
|
||||
return key.getTextDescription();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component to allow editing of the keymaps stored by a KeyPressMappingSet
|
||||
object.
|
||||
|
||||
@see KeyPressMappingSet
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API KeyMappingEditorComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a KeyMappingEditorComponent.
|
||||
|
||||
@param mappingSet this is the set of mappings to display and edit. Make sure the
|
||||
mappings object is not deleted before this component!
|
||||
@param showResetToDefaultButton if true, then at the bottom of the list, the
|
||||
component will include a 'reset to defaults' button.
|
||||
*/
|
||||
KeyMappingEditorComponent (KeyPressMappingSet& mappingSet,
|
||||
bool showResetToDefaultButton);
|
||||
|
||||
/** Destructor. */
|
||||
~KeyMappingEditorComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets up the colours to use for parts of the component.
|
||||
|
||||
@param mainBackground colour to use for most of the background
|
||||
@param textColour colour to use for the text
|
||||
*/
|
||||
void setColours (Colour mainBackground,
|
||||
Colour textColour);
|
||||
|
||||
/** Returns the KeyPressMappingSet that this component is acting upon. */
|
||||
KeyPressMappingSet& getMappings() const noexcept { return mappings; }
|
||||
|
||||
/** Returns the ApplicationCommandManager that this component is connected to. */
|
||||
ApplicationCommandManager& getCommandManager() const noexcept { return mappings.getCommandManager(); }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Can be overridden if some commands need to be excluded from the list.
|
||||
|
||||
By default this will use the KeyPressMappingSet's shouldCommandBeVisibleInEditor()
|
||||
method to decide what to return, but you can override it to handle special cases.
|
||||
*/
|
||||
virtual bool shouldCommandBeIncluded (CommandID commandID);
|
||||
|
||||
/** Can be overridden to indicate that some commands are shown as read-only.
|
||||
|
||||
By default this will use the KeyPressMappingSet's shouldCommandBeReadOnlyInEditor()
|
||||
method to decide what to return, but you can override it to handle special cases.
|
||||
*/
|
||||
virtual bool isCommandReadOnly (CommandID commandID);
|
||||
|
||||
/** This can be overridden to let you change the format of the string used
|
||||
to describe a keypress.
|
||||
|
||||
This is handy if you're using non-standard KeyPress objects, e.g. for custom
|
||||
keys that are triggered by something else externally. If you override the
|
||||
method, be sure to let the base class's method handle keys you're not
|
||||
interested in.
|
||||
*/
|
||||
virtual String getDescriptionForKeyPress (const KeyPress& key);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the editor.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x100ad00, /**< The background colour to fill the editor background. */
|
||||
textColourId = 0x100ad01, /**< The colour for the text. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
KeyPressMappingSet& mappings;
|
||||
TreeView tree;
|
||||
TextButton resetButton;
|
||||
|
||||
class TopLevelItem;
|
||||
class ChangeKeyButton;
|
||||
class MappingItem;
|
||||
class CategoryItem;
|
||||
class ItemComponent;
|
||||
std::unique_ptr<TopLevelItem> treeItem;
|
||||
ScopedMessageBox messageBox;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KeyMappingEditorComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,498 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if JUCE_ENABLE_LIVE_CONSTANT_EDITOR
|
||||
|
||||
namespace juce::LiveConstantEditor
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class AllComponentRepainter final : private Timer,
|
||||
private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
AllComponentRepainter() {}
|
||||
~AllComponentRepainter() override { clearSingletonInstance(); }
|
||||
|
||||
JUCE_DECLARE_SINGLETON_INLINE (AllComponentRepainter, false)
|
||||
|
||||
void trigger()
|
||||
{
|
||||
if (! isTimerRunning())
|
||||
startTimer (100);
|
||||
}
|
||||
|
||||
private:
|
||||
void timerCallback() override
|
||||
{
|
||||
stopTimer();
|
||||
|
||||
Array<Component*> alreadyDone;
|
||||
|
||||
for (int i = TopLevelWindow::getNumTopLevelWindows(); --i >= 0;)
|
||||
if (auto* c = TopLevelWindow::getTopLevelWindow (i))
|
||||
repaintAndResizeAllComps (c, alreadyDone);
|
||||
|
||||
auto& desktop = Desktop::getInstance();
|
||||
|
||||
for (int i = desktop.getNumComponents(); --i >= 0;)
|
||||
if (auto* c = desktop.getComponent (i))
|
||||
repaintAndResizeAllComps (c, alreadyDone);
|
||||
}
|
||||
|
||||
static void repaintAndResizeAllComps (Component::SafePointer<Component> c,
|
||||
Array<Component*>& alreadyDone)
|
||||
{
|
||||
if (c->isVisible() && ! alreadyDone.contains (c))
|
||||
{
|
||||
c->repaint();
|
||||
c->resized();
|
||||
|
||||
for (int i = c->getNumChildComponents(); --i >= 0;)
|
||||
{
|
||||
if (auto* child = c->getChildComponent (i))
|
||||
{
|
||||
repaintAndResizeAllComps (child, alreadyDone);
|
||||
alreadyDone.add (child);
|
||||
}
|
||||
|
||||
if (c == nullptr)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
int64 parseInt (String s)
|
||||
{
|
||||
s = s.trimStart();
|
||||
|
||||
if (s.startsWithChar ('-'))
|
||||
return -parseInt (s.substring (1));
|
||||
|
||||
if (s.startsWith ("0x"))
|
||||
return s.substring (2).getHexValue64();
|
||||
|
||||
return s.getLargeIntValue();
|
||||
}
|
||||
|
||||
double parseDouble (const String& s)
|
||||
{
|
||||
return s.retainCharacters ("0123456789.eE-").getDoubleValue();
|
||||
}
|
||||
|
||||
String intToString (int v, bool preferHex) { return preferHex ? "0x" + String::toHexString (v) : String (v); }
|
||||
String intToString (int64 v, bool preferHex) { return preferHex ? "0x" + String::toHexString (v) : String (v); }
|
||||
|
||||
//==============================================================================
|
||||
LiveValueBase::LiveValueBase (const char* file, int line)
|
||||
: sourceFile (file), sourceLine (line)
|
||||
{
|
||||
name = File (sourceFile).getFileName() + " : " + String (sourceLine);
|
||||
}
|
||||
|
||||
LiveValueBase::~LiveValueBase()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
LivePropertyEditorBase::LivePropertyEditorBase (LiveValueBase& v, CodeDocument& d)
|
||||
: value (v), document (d), sourceEditor (document, &tokeniser)
|
||||
{
|
||||
setSize (600, 100);
|
||||
|
||||
addAndMakeVisible (name);
|
||||
addAndMakeVisible (resetButton);
|
||||
addAndMakeVisible (valueEditor);
|
||||
addAndMakeVisible (sourceEditor);
|
||||
|
||||
findOriginalValueInCode();
|
||||
selectOriginalValue();
|
||||
|
||||
name.setFont (withDefaultMetrics (FontOptions { 13.0f }));
|
||||
name.setText (v.name, dontSendNotification);
|
||||
valueEditor.setMultiLine (v.isString());
|
||||
valueEditor.setReturnKeyStartsNewLine (v.isString());
|
||||
valueEditor.setText (v.getStringValue (wasHex), dontSendNotification);
|
||||
valueEditor.onTextChange = [this] { applyNewValue (valueEditor.getText()); };
|
||||
sourceEditor.setReadOnly (true);
|
||||
sourceEditor.setFont (sourceEditor.getFont().withHeight (13.0f));
|
||||
resetButton.onClick = [this] { applyNewValue (value.getOriginalStringValue (wasHex)); };
|
||||
}
|
||||
|
||||
void LivePropertyEditorBase::paint (Graphics& g)
|
||||
{
|
||||
g.setColour (Colours::white);
|
||||
g.fillRect (getLocalBounds().removeFromBottom (1));
|
||||
}
|
||||
|
||||
void LivePropertyEditorBase::resized()
|
||||
{
|
||||
auto r = getLocalBounds().reduced (0, 3).withTrimmedBottom (1);
|
||||
|
||||
auto left = r.removeFromLeft (jmax (200, r.getWidth() / 3));
|
||||
|
||||
auto top = left.removeFromTop (25);
|
||||
resetButton.setBounds (top.removeFromRight (35).reduced (0, 3));
|
||||
name.setBounds (top);
|
||||
|
||||
if (customComp != nullptr)
|
||||
{
|
||||
valueEditor.setBounds (left.removeFromTop (25));
|
||||
left.removeFromTop (2);
|
||||
customComp->setBounds (left);
|
||||
}
|
||||
else
|
||||
{
|
||||
valueEditor.setBounds (left);
|
||||
}
|
||||
|
||||
r.removeFromLeft (4);
|
||||
sourceEditor.setBounds (r);
|
||||
}
|
||||
|
||||
void LivePropertyEditorBase::applyNewValue (const String& s)
|
||||
{
|
||||
value.setStringValue (s);
|
||||
|
||||
document.replaceSection (valueStart.getPosition(), valueEnd.getPosition(), value.getCodeValue (wasHex));
|
||||
document.clearUndoHistory();
|
||||
selectOriginalValue();
|
||||
|
||||
valueEditor.setText (s, dontSendNotification);
|
||||
AllComponentRepainter::getInstance()->trigger();
|
||||
}
|
||||
|
||||
void LivePropertyEditorBase::selectOriginalValue()
|
||||
{
|
||||
sourceEditor.selectRegion (valueStart, valueEnd);
|
||||
}
|
||||
|
||||
void LivePropertyEditorBase::findOriginalValueInCode()
|
||||
{
|
||||
CodeDocument::Position pos (document, value.sourceLine, 0);
|
||||
auto line = pos.getLineText();
|
||||
auto p = line.getCharPointer();
|
||||
|
||||
p = CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT"));
|
||||
|
||||
if (p.isEmpty())
|
||||
{
|
||||
// Not sure how this would happen - some kind of mix-up between source code and line numbers..
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
p += (int) (sizeof ("JUCE_LIVE_CONSTANT") - 1);
|
||||
p.incrementToEndOfWhitespace();
|
||||
|
||||
if (! CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT")).isEmpty())
|
||||
{
|
||||
// Aargh! You've added two JUCE_LIVE_CONSTANT macros on the same line!
|
||||
// They're identified by their line number, so you must make sure each
|
||||
// one goes on a separate line!
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
if (p.getAndAdvance() == '(')
|
||||
{
|
||||
auto start = p, end = p;
|
||||
|
||||
int depth = 1;
|
||||
|
||||
while (! end.isEmpty())
|
||||
{
|
||||
auto c = end.getAndAdvance();
|
||||
|
||||
if (c == '(') ++depth;
|
||||
if (c == ')') --depth;
|
||||
|
||||
if (depth == 0)
|
||||
{
|
||||
--end;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (end > start)
|
||||
{
|
||||
valueStart = CodeDocument::Position (document, value.sourceLine, (int) (start - line.getCharPointer()));
|
||||
valueEnd = CodeDocument::Position (document, value.sourceLine, (int) (end - line.getCharPointer()));
|
||||
|
||||
valueStart.setPositionMaintained (true);
|
||||
valueEnd.setPositionMaintained (true);
|
||||
|
||||
wasHex = String (start, end).containsIgnoreCase ("0x");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class ValueListHolderComponent final : public Component
|
||||
{
|
||||
public:
|
||||
ValueListHolderComponent (ValueList& l) : valueList (l)
|
||||
{
|
||||
setVisible (true);
|
||||
}
|
||||
|
||||
void addItem (int width, LiveValueBase& v, CodeDocument& doc)
|
||||
{
|
||||
addAndMakeVisible (editors.add (v.createPropertyComponent (doc)));
|
||||
layout (width);
|
||||
}
|
||||
|
||||
void layout (int width)
|
||||
{
|
||||
setSize (width, editors.size() * itemHeight);
|
||||
resized();
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto r = getLocalBounds().reduced (2, 0);
|
||||
|
||||
for (int i = 0; i < editors.size(); ++i)
|
||||
editors.getUnchecked (i)->setBounds (r.removeFromTop (itemHeight));
|
||||
}
|
||||
|
||||
enum { itemHeight = 120 };
|
||||
|
||||
ValueList& valueList;
|
||||
OwnedArray<LivePropertyEditorBase> editors;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class ValueList::EditorWindow final : public DocumentWindow,
|
||||
private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
EditorWindow (ValueList& list)
|
||||
: DocumentWindow ("Live Values", Colours::lightgrey, DocumentWindow::closeButton)
|
||||
{
|
||||
setLookAndFeel (&lookAndFeel);
|
||||
setUsingNativeTitleBar (true);
|
||||
|
||||
viewport.setViewedComponent (new ValueListHolderComponent (list), true);
|
||||
viewport.setSize (700, 600);
|
||||
viewport.setScrollBarsShown (true, false);
|
||||
|
||||
setContentNonOwned (&viewport, true);
|
||||
setResizable (true, false);
|
||||
setResizeLimits (500, 400, 10000, 10000);
|
||||
centreWithSize (getWidth(), getHeight());
|
||||
setVisible (true);
|
||||
}
|
||||
|
||||
~EditorWindow() override
|
||||
{
|
||||
setLookAndFeel (nullptr);
|
||||
}
|
||||
|
||||
void closeButtonPressed() override
|
||||
{
|
||||
setVisible (false);
|
||||
}
|
||||
|
||||
void updateItems (ValueList& list)
|
||||
{
|
||||
if (auto* l = dynamic_cast<ValueListHolderComponent*> (viewport.getViewedComponent()))
|
||||
{
|
||||
while (l->getNumChildComponents() < list.values.size())
|
||||
{
|
||||
if (auto* v = list.values [l->getNumChildComponents()])
|
||||
l->addItem (viewport.getMaximumVisibleWidth(), *v, list.getDocument (v->sourceFile));
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
setVisible (true);
|
||||
}
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
DocumentWindow::resized();
|
||||
|
||||
if (auto* l = dynamic_cast<ValueListHolderComponent*> (viewport.getViewedComponent()))
|
||||
l->layout (viewport.getMaximumVisibleWidth());
|
||||
}
|
||||
|
||||
Viewport viewport;
|
||||
LookAndFeel_V3 lookAndFeel;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ValueList::ValueList() {}
|
||||
ValueList::~ValueList() { clearSingletonInstance(); }
|
||||
|
||||
void ValueList::addValue (LiveValueBase* v)
|
||||
{
|
||||
values.add (v);
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
void ValueList::handleAsyncUpdate()
|
||||
{
|
||||
if (editorWindow == nullptr)
|
||||
editorWindow = new EditorWindow (*this);
|
||||
|
||||
editorWindow->updateItems (*this);
|
||||
}
|
||||
|
||||
CodeDocument& ValueList::getDocument (const File& file)
|
||||
{
|
||||
const int index = documentFiles.indexOf (file.getFullPathName());
|
||||
|
||||
if (index >= 0)
|
||||
return *documents.getUnchecked (index);
|
||||
|
||||
auto* doc = documents.add (new CodeDocument());
|
||||
documentFiles.add (file);
|
||||
doc->replaceAllContent (file.loadFileAsString());
|
||||
doc->clearUndoHistory();
|
||||
return *doc;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct ColourEditorComp final : public Component,
|
||||
private ChangeListener
|
||||
{
|
||||
ColourEditorComp (LivePropertyEditorBase& e) : editor (e)
|
||||
{
|
||||
setMouseCursor (MouseCursor::PointingHandCursor);
|
||||
}
|
||||
|
||||
Colour getColour() const
|
||||
{
|
||||
return Colour ((uint32) parseInt (editor.value.getStringValue (false)));
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillCheckerBoard (getLocalBounds().toFloat(), 6.0f, 6.0f,
|
||||
Colour (0xffdddddd).overlaidWith (getColour()),
|
||||
Colour (0xffffffff).overlaidWith (getColour()));
|
||||
}
|
||||
|
||||
void mouseDown (const MouseEvent&) override
|
||||
{
|
||||
auto colourSelector = std::make_unique<ColourSelector>();
|
||||
colourSelector->setName ("Colour");
|
||||
colourSelector->setCurrentColour (getColour());
|
||||
colourSelector->addChangeListener (this);
|
||||
colourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
|
||||
colourSelector->setSize (300, 400);
|
||||
|
||||
CallOutBox::launchAsynchronously (std::move (colourSelector), getScreenBounds(), nullptr);
|
||||
}
|
||||
|
||||
void changeListenerCallback (ChangeBroadcaster* source) override
|
||||
{
|
||||
if (auto* cs = dynamic_cast<ColourSelector*> (source))
|
||||
editor.applyNewValue (getAsString (cs->getCurrentColour(), true));
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
LivePropertyEditorBase& editor;
|
||||
};
|
||||
|
||||
Component* createColourEditor (LivePropertyEditorBase& editor)
|
||||
{
|
||||
return new ColourEditorComp (editor);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct SliderComp : public Component
|
||||
{
|
||||
SliderComp (LivePropertyEditorBase& e, bool useFloat)
|
||||
: editor (e), isFloat (useFloat)
|
||||
{
|
||||
slider.setTextBoxStyle (Slider::NoTextBox, true, 0, 0);
|
||||
addAndMakeVisible (slider);
|
||||
updateRange();
|
||||
slider.onDragEnd = [this] { updateRange(); };
|
||||
slider.onValueChange = [this]
|
||||
{
|
||||
editor.applyNewValue (isFloat ? getAsString ((double) slider.getValue(), editor.wasHex)
|
||||
: getAsString ((int64) slider.getValue(), editor.wasHex));
|
||||
};
|
||||
}
|
||||
|
||||
virtual void updateRange()
|
||||
{
|
||||
double v = isFloat ? parseDouble (editor.value.getStringValue (false))
|
||||
: (double) parseInt (editor.value.getStringValue (false));
|
||||
|
||||
double range = isFloat ? 10 : 100;
|
||||
|
||||
slider.setRange (v - range, v + range);
|
||||
slider.setValue (v, dontSendNotification);
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
slider.setBounds (getLocalBounds().removeFromTop (25));
|
||||
}
|
||||
|
||||
LivePropertyEditorBase& editor;
|
||||
Slider slider;
|
||||
bool isFloat;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct BoolSliderComp final : public SliderComp
|
||||
{
|
||||
BoolSliderComp (LivePropertyEditorBase& e)
|
||||
: SliderComp (e, false)
|
||||
{
|
||||
slider.onValueChange = [this] { editor.applyNewValue (slider.getValue() > 0.5 ? "true" : "false"); };
|
||||
}
|
||||
|
||||
void updateRange() override
|
||||
{
|
||||
slider.setRange (0.0, 1.0, dontSendNotification);
|
||||
slider.setValue (editor.value.getStringValue (false) == "true", dontSendNotification);
|
||||
}
|
||||
};
|
||||
|
||||
Component* createIntegerSlider (LivePropertyEditorBase& editor) { return new SliderComp (editor, false); }
|
||||
Component* createFloatSlider (LivePropertyEditorBase& editor) { return new SliderComp (editor, true); }
|
||||
Component* createBoolSlider (LivePropertyEditorBase& editor) { return new BoolSliderComp (editor); }
|
||||
|
||||
} // namespace juce::LiveConstantEditor
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if JUCE_ENABLE_LIVE_CONSTANT_EDITOR && ! defined (DOXYGEN)
|
||||
|
||||
//==============================================================================
|
||||
/** You can safely ignore all the stuff in this namespace - it's a bunch of boilerplate
|
||||
code used to implement the JUCE_LIVE_CONSTANT functionality.
|
||||
*/
|
||||
namespace juce::LiveConstantEditor
|
||||
{
|
||||
int64 parseInt (String);
|
||||
double parseDouble (const String&);
|
||||
String intToString (int, bool preferHex);
|
||||
String intToString (int64, bool preferHex);
|
||||
|
||||
template <typename Type>
|
||||
static void setFromString (Type& v, const String& s) { v = static_cast<Type> (s); }
|
||||
inline void setFromString (char& v, const String& s) { v = (char) parseInt (s); }
|
||||
inline void setFromString (unsigned char& v, const String& s) { v = (unsigned char) parseInt (s); }
|
||||
inline void setFromString (short& v, const String& s) { v = (short) parseInt (s); }
|
||||
inline void setFromString (unsigned short& v, const String& s) { v = (unsigned short) parseInt (s); }
|
||||
inline void setFromString (int& v, const String& s) { v = (int) parseInt (s); }
|
||||
inline void setFromString (unsigned int& v, const String& s) { v = (unsigned int) parseInt (s); }
|
||||
inline void setFromString (long& v, const String& s) { v = (long) parseInt (s); }
|
||||
inline void setFromString (unsigned long& v, const String& s) { v = (unsigned long) parseInt (s); }
|
||||
inline void setFromString (int64& v, const String& s) { v = (int64) parseInt (s); }
|
||||
inline void setFromString (uint64& v, const String& s) { v = (uint64) parseInt (s); }
|
||||
inline void setFromString (double& v, const String& s) { v = parseDouble (s); }
|
||||
inline void setFromString (float& v, const String& s) { v = (float) parseDouble (s); }
|
||||
inline void setFromString (bool& v, const String& s) { v = (s == "true"); }
|
||||
inline void setFromString (String& v, const String& s) { v = s; }
|
||||
inline void setFromString (Colour& v, const String& s) { v = Colour ((uint32) parseInt (s)); }
|
||||
|
||||
template <typename Type>
|
||||
inline String getAsString (const Type& v, bool) { return String (v); }
|
||||
inline String getAsString (char v, bool preferHex) { return intToString ((int) v, preferHex); }
|
||||
inline String getAsString (unsigned char v, bool preferHex) { return intToString ((int) v, preferHex); }
|
||||
inline String getAsString (short v, bool preferHex) { return intToString ((int) v, preferHex); }
|
||||
inline String getAsString (unsigned short v, bool preferHex) { return intToString ((int) v, preferHex); }
|
||||
inline String getAsString (int v, bool preferHex) { return intToString ((int) v, preferHex); }
|
||||
inline String getAsString (unsigned int v, bool preferHex) { return intToString ((int) v, preferHex); }
|
||||
inline String getAsString (bool v, bool) { return v ? "true" : "false"; }
|
||||
inline String getAsString (int64 v, bool preferHex) { return intToString ((int64) v, preferHex); }
|
||||
inline String getAsString (uint64 v, bool preferHex) { return intToString ((int64) v, preferHex); }
|
||||
inline String getAsString (Colour v, bool) { return intToString ((int) v.getARGB(), true); }
|
||||
|
||||
template <typename Type> struct isStringType { enum { value = 0 }; };
|
||||
template <> struct isStringType<String> { enum { value = 1 }; };
|
||||
|
||||
template <typename Type>
|
||||
inline String getAsCode (Type& v, bool preferHex) { return getAsString (v, preferHex); }
|
||||
inline String getAsCode (Colour v, bool) { return "Colour (0x" + String::toHexString ((int) v.getARGB()).paddedLeft ('0', 8) + ")"; }
|
||||
inline String getAsCode (const String& v, bool) { return CppTokeniserFunctions::addEscapeChars (v).quoted(); }
|
||||
inline String getAsCode (const char* v, bool) { return getAsCode (String (v), false); }
|
||||
|
||||
template <typename Type>
|
||||
inline const char* castToCharPointer (const Type&) { return ""; }
|
||||
inline const char* castToCharPointer (const String& s) { return s.toRawUTF8(); }
|
||||
|
||||
struct LivePropertyEditorBase;
|
||||
|
||||
//==============================================================================
|
||||
struct JUCE_API LiveValueBase
|
||||
{
|
||||
LiveValueBase (const char* file, int line);
|
||||
virtual ~LiveValueBase();
|
||||
|
||||
virtual LivePropertyEditorBase* createPropertyComponent (CodeDocument&) = 0;
|
||||
virtual String getStringValue (bool preferHex) const = 0;
|
||||
virtual String getCodeValue (bool preferHex) const = 0;
|
||||
virtual void setStringValue (const String&) = 0;
|
||||
virtual String getOriginalStringValue (bool preferHex) const = 0;
|
||||
virtual bool isString() const = 0;
|
||||
|
||||
String name, sourceFile;
|
||||
int sourceLine;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (LiveValueBase)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct JUCE_API LivePropertyEditorBase : public Component
|
||||
{
|
||||
LivePropertyEditorBase (LiveValueBase&, CodeDocument&);
|
||||
|
||||
void paint (Graphics&) override;
|
||||
void resized() override;
|
||||
|
||||
void applyNewValue (const String&);
|
||||
void selectOriginalValue();
|
||||
void findOriginalValueInCode();
|
||||
|
||||
LiveValueBase& value;
|
||||
Label name;
|
||||
TextEditor valueEditor;
|
||||
TextButton resetButton { "reset" };
|
||||
CodeDocument& document;
|
||||
CPlusPlusCodeTokeniser tokeniser;
|
||||
CodeEditorComponent sourceEditor;
|
||||
CodeDocument::Position valueStart, valueEnd;
|
||||
std::unique_ptr<Component> customComp;
|
||||
bool wasHex = false;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (LivePropertyEditorBase)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
Component* createColourEditor (LivePropertyEditorBase&);
|
||||
Component* createIntegerSlider (LivePropertyEditorBase&);
|
||||
Component* createFloatSlider (LivePropertyEditorBase&);
|
||||
Component* createBoolSlider (LivePropertyEditorBase&);
|
||||
|
||||
template <typename Type> struct CustomEditor { static Component* create (LivePropertyEditorBase&) { return nullptr; } };
|
||||
template <> struct CustomEditor<char> { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } };
|
||||
template <> struct CustomEditor<unsigned char> { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } };
|
||||
template <> struct CustomEditor<int> { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } };
|
||||
template <> struct CustomEditor<unsigned int> { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } };
|
||||
template <> struct CustomEditor<short> { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } };
|
||||
template <> struct CustomEditor<unsigned short> { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } };
|
||||
template <> struct CustomEditor<int64> { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } };
|
||||
template <> struct CustomEditor<uint64> { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } };
|
||||
template <> struct CustomEditor<float> { static Component* create (LivePropertyEditorBase& e) { return createFloatSlider (e); } };
|
||||
template <> struct CustomEditor<double> { static Component* create (LivePropertyEditorBase& e) { return createFloatSlider (e); } };
|
||||
template <> struct CustomEditor<Colour> { static Component* create (LivePropertyEditorBase& e) { return createColourEditor (e); } };
|
||||
template <> struct CustomEditor<bool> { static Component* create (LivePropertyEditorBase& e) { return createBoolSlider (e); } };
|
||||
|
||||
template <typename Type>
|
||||
struct LivePropertyEditor : public LivePropertyEditorBase
|
||||
{
|
||||
template <typename ValueType>
|
||||
LivePropertyEditor (ValueType& v, CodeDocument& d) : LivePropertyEditorBase (v, d)
|
||||
{
|
||||
customComp.reset (CustomEditor<Type>::create (*this));
|
||||
addAndMakeVisible (customComp.get());
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
struct LiveValue : public LiveValueBase
|
||||
{
|
||||
LiveValue (const char* file, int line, const Type& initialValue)
|
||||
: LiveValueBase (file, line), value (initialValue), originalValue (initialValue)
|
||||
{}
|
||||
|
||||
operator Type() const noexcept { return value; }
|
||||
Type get() const noexcept { return value; }
|
||||
operator const char*() const { return castToCharPointer (value); }
|
||||
|
||||
LivePropertyEditorBase* createPropertyComponent (CodeDocument& doc) override
|
||||
{
|
||||
return new LivePropertyEditor<Type> (*this, doc);
|
||||
}
|
||||
|
||||
String getStringValue (bool preferHex) const override { return getAsString (value, preferHex); }
|
||||
String getCodeValue (bool preferHex) const override { return getAsCode (value, preferHex); }
|
||||
String getOriginalStringValue (bool preferHex) const override { return getAsString (originalValue, preferHex); }
|
||||
void setStringValue (const String& s) override { setFromString (value, s); }
|
||||
bool isString() const override { return isStringType<Type>::value; }
|
||||
|
||||
Type value, originalValue;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (LiveValue)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class JUCE_API ValueList : private AsyncUpdater,
|
||||
private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
ValueList();
|
||||
~ValueList() override;
|
||||
|
||||
JUCE_DECLARE_SINGLETON_INLINE (ValueList, false)
|
||||
|
||||
template <typename Type>
|
||||
LiveValue<Type>& getValue (const char* file, int line, const Type& initialValue)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
using ValueType = LiveValue<Type>;
|
||||
|
||||
for (auto* v : values)
|
||||
if (v->sourceLine == line && v->sourceFile == file)
|
||||
return *static_cast<ValueType*> (v);
|
||||
|
||||
auto v = new ValueType (file, line, initialValue);
|
||||
addValue (v);
|
||||
return *v;
|
||||
}
|
||||
|
||||
private:
|
||||
OwnedArray<LiveValueBase> values;
|
||||
OwnedArray<CodeDocument> documents;
|
||||
Array<File> documentFiles;
|
||||
class EditorWindow;
|
||||
Component::SafePointer<EditorWindow> editorWindow;
|
||||
CriticalSection lock;
|
||||
|
||||
CodeDocument& getDocument (const File&);
|
||||
void addValue (LiveValueBase*);
|
||||
void handleAsyncUpdate() override;
|
||||
};
|
||||
|
||||
template <typename Type>
|
||||
inline LiveValue<Type>& getValue (const char* file, int line, const Type& initialValue)
|
||||
{
|
||||
// If you hit this assertion then the __FILE__ macro is providing a
|
||||
// relative path instead of an absolute path. On Windows this will be
|
||||
// a path relative to the build directory rather than the currently
|
||||
// running application. To fix this you must compile with the /FC flag.
|
||||
jassert (File::isAbsolutePath (file));
|
||||
|
||||
return ValueList::getInstance()->getValue (file, line, initialValue);
|
||||
}
|
||||
|
||||
inline LiveValue<String>& getValue (const char* file, int line, const char* initialValue)
|
||||
{
|
||||
return getValue (file, line, String (initialValue));
|
||||
}
|
||||
|
||||
} // namespace juce::LiveConstantEditor
|
||||
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_ENABLE_LIVE_CONSTANT_EDITOR || DOXYGEN
|
||||
/**
|
||||
This macro wraps a primitive constant value in some cunning boilerplate code that allows
|
||||
its value to be interactively tweaked in a popup window while your application is running.
|
||||
|
||||
In a release build, this macro disappears and is replaced by only the constant that it
|
||||
wraps, but if JUCE_ENABLE_LIVE_CONSTANT_EDITOR is enabled, it injects a class wrapper
|
||||
that automatically pops-up a window containing an editor that allows the value to be
|
||||
tweaked at run-time. The editor window will also force all visible components to be
|
||||
resized and repainted whenever a value is changed, so that if you use this to wrap
|
||||
a colour or layout parameter, you'll be able to immediately see the effects of changing it.
|
||||
|
||||
The editor will also load the original source-file that contains each JUCE_LIVE_CONSTANT
|
||||
macro, and will display a preview of the modified source code as you adjust the values.
|
||||
|
||||
Things to note:
|
||||
|
||||
- Only one of these per line! The __FILE__ and __LINE__ macros are used to identify
|
||||
the value, so things will get confused if you have more than one per line
|
||||
- Obviously because it needs to load the source code based on the __FILE__ macro,
|
||||
it'll only work if the source files are stored locally in the same location as they
|
||||
were when you compiled the program.
|
||||
- It's only designed to cope with simple types: primitives, string literals, and
|
||||
the Colour class, so if you try using it for other classes or complex expressions,
|
||||
good luck!
|
||||
- The editor window will get popped up whenever a new value is used for the first
|
||||
time. You can close the window, but there's no way to get it back without restarting
|
||||
the app!
|
||||
|
||||
e.g. in this example the colours, font size, and text used in the paint method can
|
||||
all be adjusted live:
|
||||
@code
|
||||
void MyComp::paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (JUCE_LIVE_CONSTANT (Colour (0xffddddff)));
|
||||
|
||||
Colour fontColour = JUCE_LIVE_CONSTANT (Colour (0xff005500));
|
||||
float fontSize = JUCE_LIVE_CONSTANT (16.0f);
|
||||
|
||||
g.setColour (fontColour);
|
||||
g.setFont (fontSize);
|
||||
|
||||
g.drawFittedText (JUCE_LIVE_CONSTANT ("Hello world!"),
|
||||
getLocalBounds(), Justification::centred, 2);
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
#define JUCE_LIVE_CONSTANT(initialValue) \
|
||||
(juce::LiveConstantEditor::getValue (__FILE__, __LINE__ - 1, initialValue).get())
|
||||
#else
|
||||
#define JUCE_LIVE_CONSTANT(initialValue) \
|
||||
(initialValue)
|
||||
#endif
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
PreferencesPanel::PreferencesPanel()
|
||||
: buttonSize (70)
|
||||
{
|
||||
}
|
||||
|
||||
PreferencesPanel::~PreferencesPanel()
|
||||
{
|
||||
}
|
||||
|
||||
int PreferencesPanel::getButtonSize() const noexcept
|
||||
{
|
||||
return buttonSize;
|
||||
}
|
||||
|
||||
void PreferencesPanel::setButtonSize (int newSize)
|
||||
{
|
||||
buttonSize = newSize;
|
||||
resized();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void PreferencesPanel::addSettingsPage (const String& title,
|
||||
const Drawable* icon,
|
||||
const Drawable* overIcon,
|
||||
const Drawable* downIcon)
|
||||
{
|
||||
auto* button = new DrawableButton (title, DrawableButton::ImageAboveTextLabel);
|
||||
buttons.add (button);
|
||||
|
||||
button->setImages (icon, overIcon, downIcon);
|
||||
button->setRadioGroupId (1);
|
||||
button->onClick = [this] { clickedPage(); };
|
||||
button->setClickingTogglesState (true);
|
||||
button->setWantsKeyboardFocus (false);
|
||||
addAndMakeVisible (button);
|
||||
|
||||
resized();
|
||||
|
||||
if (currentPage == nullptr)
|
||||
setCurrentPage (title);
|
||||
}
|
||||
|
||||
void PreferencesPanel::addSettingsPage (const String& title, const void* imageData, int imageDataSize)
|
||||
{
|
||||
DrawableImage icon, iconOver, iconDown;
|
||||
icon.setImage (ImageCache::getFromMemory (imageData, imageDataSize));
|
||||
|
||||
iconOver.setImage (ImageCache::getFromMemory (imageData, imageDataSize));
|
||||
iconOver.setOverlayColour (Colours::black.withAlpha (0.12f));
|
||||
|
||||
iconDown.setImage (ImageCache::getFromMemory (imageData, imageDataSize));
|
||||
iconDown.setOverlayColour (Colours::black.withAlpha (0.25f));
|
||||
|
||||
addSettingsPage (title, &icon, &iconOver, &iconDown);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void PreferencesPanel::showInDialogBox (const String& dialogTitle, int dialogWidth, int dialogHeight, Colour backgroundColour)
|
||||
{
|
||||
setSize (dialogWidth, dialogHeight);
|
||||
|
||||
DialogWindow::LaunchOptions o;
|
||||
o.content.setNonOwned (this);
|
||||
o.dialogTitle = dialogTitle;
|
||||
o.dialogBackgroundColour = backgroundColour;
|
||||
o.escapeKeyTriggersCloseButton = false;
|
||||
o.useNativeTitleBar = false;
|
||||
o.resizable = false;
|
||||
|
||||
o.launchAsync();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void PreferencesPanel::resized()
|
||||
{
|
||||
for (int i = 0; i < buttons.size(); ++i)
|
||||
buttons.getUnchecked (i)->setBounds (i * buttonSize, 0, buttonSize, buttonSize);
|
||||
|
||||
if (currentPage != nullptr)
|
||||
currentPage->setBounds (getLocalBounds().withTop (buttonSize + 5));
|
||||
}
|
||||
|
||||
void PreferencesPanel::paint (Graphics& g)
|
||||
{
|
||||
g.setColour (Colours::grey);
|
||||
g.fillRect (0, buttonSize + 2, getWidth(), 1);
|
||||
}
|
||||
|
||||
void PreferencesPanel::setCurrentPage (const String& pageName)
|
||||
{
|
||||
if (currentPageName != pageName)
|
||||
{
|
||||
currentPageName = pageName;
|
||||
|
||||
currentPage.reset();
|
||||
currentPage.reset (createComponentForPage (pageName));
|
||||
|
||||
if (currentPage != nullptr)
|
||||
{
|
||||
addAndMakeVisible (currentPage.get());
|
||||
currentPage->toBack();
|
||||
resized();
|
||||
}
|
||||
|
||||
for (auto* b : buttons)
|
||||
{
|
||||
if (b->getName() == pageName)
|
||||
{
|
||||
b->setToggleState (true, dontSendNotification);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PreferencesPanel::clickedPage()
|
||||
{
|
||||
for (auto* b : buttons)
|
||||
{
|
||||
if (b->getToggleState())
|
||||
{
|
||||
setCurrentPage (b->getName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component with a set of buttons at the top for changing between pages of
|
||||
preferences.
|
||||
|
||||
This is just a handy way of writing a Mac-style preferences panel where you
|
||||
have a row of buttons along the top for the different preference categories,
|
||||
each button having an icon above its name. Clicking these will show an
|
||||
appropriate prefs page below it.
|
||||
|
||||
You can either put one of these inside your own component, or just use the
|
||||
showInDialogBox() method to show it in a window and run it modally.
|
||||
|
||||
To use it, just add a set of named pages with the addSettingsPage() method,
|
||||
and implement the createComponentForPage() method to create suitable components
|
||||
for each of these pages.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API PreferencesPanel : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty panel.
|
||||
|
||||
Use addSettingsPage() to add some pages to it in your constructor.
|
||||
*/
|
||||
PreferencesPanel();
|
||||
|
||||
/** Destructor. */
|
||||
~PreferencesPanel() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a page using a set of drawables to define the page's icon.
|
||||
|
||||
Note that the other version of this method is much easier if you're using
|
||||
an image instead of a custom drawable.
|
||||
|
||||
@param pageTitle the name of this preferences page - you'll need to
|
||||
make sure your createComponentForPage() method creates
|
||||
a suitable component when it is passed this name
|
||||
@param normalIcon the drawable to display in the page's button normally
|
||||
@param overIcon the drawable to display in the page's button when the mouse is over
|
||||
@param downIcon the drawable to display in the page's button when the button is down
|
||||
@see DrawableButton
|
||||
*/
|
||||
void addSettingsPage (const String& pageTitle,
|
||||
const Drawable* normalIcon,
|
||||
const Drawable* overIcon,
|
||||
const Drawable* downIcon);
|
||||
|
||||
/** Creates a page using a set of drawables to define the page's icon.
|
||||
|
||||
The other version of this method gives you more control over the icon, but this
|
||||
one is much easier if you're just loading it from a file.
|
||||
|
||||
@param pageTitle the name of this preferences page - you'll need to
|
||||
make sure your createComponentForPage() method creates
|
||||
a suitable component when it is passed this name
|
||||
@param imageData a block of data containing an image file, e.g. a jpeg, png or gif.
|
||||
For this to look good, you'll probably want to use a nice
|
||||
transparent png file.
|
||||
@param imageDataSize the size of the image data, in bytes
|
||||
*/
|
||||
void addSettingsPage (const String& pageTitle,
|
||||
const void* imageData,
|
||||
int imageDataSize);
|
||||
|
||||
/** Utility method to display this panel in a DialogWindow.
|
||||
|
||||
Calling this will create a DialogWindow containing this panel with the
|
||||
given size and title, and will run it modally, returning when the user
|
||||
closes the dialog box.
|
||||
*/
|
||||
void showInDialogBox (const String& dialogTitle,
|
||||
int dialogWidth,
|
||||
int dialogHeight,
|
||||
Colour backgroundColour = Colours::white);
|
||||
|
||||
//==============================================================================
|
||||
/** Subclasses must override this to return a component for each preferences page.
|
||||
|
||||
The subclass should return a pointer to a new component representing the named
|
||||
page, which the panel will then display.
|
||||
|
||||
The panel will delete the component later when the user goes to another page
|
||||
or deletes the panel.
|
||||
*/
|
||||
virtual Component* createComponentForPage (const String& pageName) = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the current page being displayed. */
|
||||
void setCurrentPage (const String& pageName);
|
||||
|
||||
/** Returns the size of the buttons shown along the top. */
|
||||
int getButtonSize() const noexcept;
|
||||
|
||||
/** Changes the size of the buttons shown along the top. */
|
||||
void setButtonSize (int newSize);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
String currentPageName;
|
||||
std::unique_ptr<Component> currentPage;
|
||||
OwnedArray<DrawableButton> buttons;
|
||||
int buttonSize;
|
||||
|
||||
void clickedPage();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PreferencesPanel)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
#if ! JUCE_PUSH_NOTIFICATIONS_IMPL
|
||||
|
||||
struct PushNotifications::Impl
|
||||
{
|
||||
explicit Impl (PushNotifications& o) : owner (o) {}
|
||||
|
||||
void requestPermissionsWithSettings (const Settings&) const
|
||||
{
|
||||
owner.listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); });
|
||||
}
|
||||
|
||||
void requestSettingsUsed() const
|
||||
{
|
||||
owner.listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); });
|
||||
}
|
||||
|
||||
bool areNotificationsEnabled() const { return false; }
|
||||
void getDeliveredNotifications() const {}
|
||||
void removeAllDeliveredNotifications() const {}
|
||||
String getDeviceToken() const { return {}; }
|
||||
void setupChannels (const Array<ChannelGroup>&, const Array<Channel>&) const {}
|
||||
void getPendingLocalNotifications() const {}
|
||||
void removeAllPendingLocalNotifications() const {}
|
||||
void subscribeToTopic (const String&) const {}
|
||||
void unsubscribeFromTopic (const String&) const {}
|
||||
void sendLocalNotification (const Notification&) const {}
|
||||
void removeDeliveredNotification (const String&) const {}
|
||||
void removePendingLocalNotification (const String&) const {}
|
||||
void sendUpstreamMessage (const String&,
|
||||
const String&,
|
||||
const String&,
|
||||
const String&,
|
||||
int,
|
||||
const StringPairArray&) const {}
|
||||
|
||||
private:
|
||||
PushNotifications& owner;
|
||||
};
|
||||
|
||||
bool PushNotifications::Notification::isValid() const noexcept { return true; }
|
||||
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
PushNotifications::Notification::Notification (const Notification& other)
|
||||
: identifier (other.identifier),
|
||||
title (other.title),
|
||||
body (other.body),
|
||||
subtitle (other.subtitle),
|
||||
groupId (other.groupId),
|
||||
badgeNumber (other.badgeNumber),
|
||||
soundToPlay (other.soundToPlay),
|
||||
properties (other.properties),
|
||||
category (other.category),
|
||||
triggerIntervalSec (other.triggerIntervalSec),
|
||||
repeat (other.repeat),
|
||||
icon (other.icon),
|
||||
channelId (other.channelId),
|
||||
largeIcon (other.largeIcon),
|
||||
tickerText (other.tickerText),
|
||||
actions (other.actions),
|
||||
progress (other.progress),
|
||||
person (other.person),
|
||||
type (other.type),
|
||||
priority (other.priority),
|
||||
lockScreenAppearance (other.lockScreenAppearance),
|
||||
publicVersion (other.publicVersion.get() != nullptr ? new Notification (*other.publicVersion) : nullptr),
|
||||
groupSortKey (other.groupSortKey),
|
||||
groupSummary (other.groupSummary),
|
||||
accentColour (other.accentColour),
|
||||
ledColour (other.ledColour),
|
||||
ledBlinkPattern (other.ledBlinkPattern),
|
||||
vibrationPattern (other.vibrationPattern),
|
||||
shouldAutoCancel (other.shouldAutoCancel),
|
||||
localOnly (other.localOnly),
|
||||
ongoing (other.ongoing),
|
||||
alertOnlyOnce (other.alertOnlyOnce),
|
||||
timestampVisibility (other.timestampVisibility),
|
||||
badgeIconType (other.badgeIconType),
|
||||
groupAlertBehaviour (other.groupAlertBehaviour),
|
||||
timeoutAfterMs (other.timeoutAfterMs)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
PushNotifications::PushNotifications()
|
||||
: pimpl (new Impl (*this))
|
||||
{
|
||||
}
|
||||
|
||||
PushNotifications::~PushNotifications() { clearSingletonInstance(); }
|
||||
|
||||
void PushNotifications::addListener (Listener* l) { listeners.add (l); }
|
||||
void PushNotifications::removeListener (Listener* l) { listeners.remove (l); }
|
||||
|
||||
void PushNotifications::requestPermissionsWithSettings (const Settings& settings)
|
||||
{
|
||||
pimpl->requestPermissionsWithSettings (settings);
|
||||
}
|
||||
|
||||
void PushNotifications::requestSettingsUsed()
|
||||
{
|
||||
pimpl->requestSettingsUsed();
|
||||
}
|
||||
|
||||
bool PushNotifications::areNotificationsEnabled() const
|
||||
{
|
||||
return pimpl->areNotificationsEnabled();
|
||||
}
|
||||
|
||||
void PushNotifications::getDeliveredNotifications() const
|
||||
{
|
||||
pimpl->getDeliveredNotifications();
|
||||
}
|
||||
|
||||
void PushNotifications::removeAllDeliveredNotifications()
|
||||
{
|
||||
pimpl->removeAllDeliveredNotifications();
|
||||
}
|
||||
|
||||
String PushNotifications::getDeviceToken() const
|
||||
{
|
||||
return pimpl->getDeviceToken();
|
||||
}
|
||||
|
||||
void PushNotifications::setupChannels (const Array<ChannelGroup>& groups,
|
||||
const Array<Channel>& channels)
|
||||
{
|
||||
pimpl->setupChannels (groups, channels);
|
||||
}
|
||||
|
||||
void PushNotifications::getPendingLocalNotifications() const
|
||||
{
|
||||
pimpl->getPendingLocalNotifications();
|
||||
}
|
||||
|
||||
void PushNotifications::removeAllPendingLocalNotifications()
|
||||
{
|
||||
pimpl->removeAllPendingLocalNotifications();
|
||||
}
|
||||
|
||||
void PushNotifications::subscribeToTopic (const String& topic)
|
||||
{
|
||||
pimpl->subscribeToTopic (topic);
|
||||
}
|
||||
|
||||
void PushNotifications::unsubscribeFromTopic (const String& topic)
|
||||
{
|
||||
pimpl->unsubscribeFromTopic (topic);
|
||||
}
|
||||
|
||||
void PushNotifications::sendLocalNotification (const Notification& n)
|
||||
{
|
||||
pimpl->sendLocalNotification (n);
|
||||
}
|
||||
|
||||
void PushNotifications::removeDeliveredNotification (const String& identifier)
|
||||
{
|
||||
pimpl->removeDeliveredNotification (identifier);
|
||||
}
|
||||
|
||||
void PushNotifications::removePendingLocalNotification (const String& identifier)
|
||||
{
|
||||
pimpl->removePendingLocalNotification (identifier);
|
||||
}
|
||||
|
||||
void PushNotifications::sendUpstreamMessage (const String& serverSenderId,
|
||||
const String& collapseKey,
|
||||
const String& messageId,
|
||||
const String& messageType,
|
||||
int timeToLive,
|
||||
const StringPairArray& additionalData)
|
||||
{
|
||||
pimpl->sendUpstreamMessage (serverSenderId,
|
||||
collapseKey,
|
||||
messageId,
|
||||
messageType,
|
||||
timeToLive,
|
||||
additionalData);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void PushNotifications::Listener::notificationSettingsReceived ([[maybe_unused]] const Settings& settings) {}
|
||||
void PushNotifications::Listener::pendingLocalNotificationsListReceived ([[maybe_unused]] const Array<Notification>& notifications) {}
|
||||
void PushNotifications::Listener::handleNotification ([[maybe_unused]] bool isLocalNotification,
|
||||
[[maybe_unused]] const Notification& notification) {}
|
||||
void PushNotifications::Listener::handleNotificationAction ([[maybe_unused]] bool isLocalNotification,
|
||||
[[maybe_unused]] const Notification& notification,
|
||||
[[maybe_unused]] const String& actionIdentifier,
|
||||
[[maybe_unused]] const String& optionalResponse) {}
|
||||
void PushNotifications::Listener::localNotificationDismissedByUser ([[maybe_unused]] const Notification& notification) {}
|
||||
void PushNotifications::Listener::deliveredNotificationsListReceived ([[maybe_unused]] const Array<Notification>& notifications) {}
|
||||
void PushNotifications::Listener::deviceTokenRefreshed ([[maybe_unused]] const String& token) {}
|
||||
void PushNotifications::Listener::remoteNotificationsDeleted() {}
|
||||
void PushNotifications::Listener::upstreamMessageSent ([[maybe_unused]] const String& messageId) {}
|
||||
void PushNotifications::Listener::upstreamMessageSendingError ([[maybe_unused]] const String& messageId,
|
||||
[[maybe_unused]] const String& error) {}
|
||||
|
||||
//==============================================================================
|
||||
void privatePostSystemNotification (const String& notificationTitle, const String& notificationBody);
|
||||
void privatePostSystemNotification ([[maybe_unused]] const String& notificationTitle,
|
||||
[[maybe_unused]] const String& notificationBody)
|
||||
{
|
||||
#if JUCE_PUSH_NOTIFICATIONS
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
auto* notificationsInstance = PushNotifications::getInstance();
|
||||
|
||||
if (notificationsInstance == nullptr)
|
||||
return;
|
||||
|
||||
#if JUCE_ANDROID
|
||||
notificationsInstance->requestPermissionsWithSettings ({});
|
||||
|
||||
static auto channels = std::invoke ([]() -> Array<PushNotifications::Channel>
|
||||
{
|
||||
PushNotifications::Channel chan;
|
||||
|
||||
chan.identifier = "1";
|
||||
chan.name = "Notifications";
|
||||
chan.description = "Accessibility notifications";
|
||||
chan.groupId = "accessibility";
|
||||
chan.ledColour = Colours::yellow;
|
||||
chan.canShowBadge = true;
|
||||
chan.enableLights = true;
|
||||
chan.enableVibration = true;
|
||||
chan.soundToPlay = URL ("default_os_sound");
|
||||
chan.vibrationPattern = { 1000, 1000 };
|
||||
|
||||
return { chan };
|
||||
});
|
||||
|
||||
notificationsInstance->setupChannels ({ PushNotifications::ChannelGroup { "accessibility", "accessibility" } },
|
||||
channels);
|
||||
#else
|
||||
static auto settings = std::invoke ([]
|
||||
{
|
||||
PushNotifications::Settings s;
|
||||
s.allowAlert = true;
|
||||
s.allowBadge = true;
|
||||
s.allowSound = true;
|
||||
|
||||
#if JUCE_IOS
|
||||
PushNotifications::Settings::Category c;
|
||||
c.identifier = "Accessibility";
|
||||
|
||||
s.categories = { c };
|
||||
#endif
|
||||
|
||||
return s;
|
||||
});
|
||||
|
||||
notificationsInstance->requestPermissionsWithSettings (settings);
|
||||
#endif
|
||||
|
||||
const auto notification = std::invoke ([¬ificationTitle, ¬ificationBody]
|
||||
{
|
||||
PushNotifications::Notification n;
|
||||
|
||||
n.identifier = String (Random::getSystemRandom().nextInt());
|
||||
n.title = notificationTitle;
|
||||
n.body = notificationBody;
|
||||
|
||||
#if JUCE_IOS
|
||||
n.category = "Accessibility";
|
||||
#elif JUCE_ANDROID
|
||||
n.channelId = "1";
|
||||
n.icon = "accessibilitynotificationicon";
|
||||
#endif
|
||||
|
||||
return n;
|
||||
});
|
||||
|
||||
if (notification.isValid())
|
||||
notificationsInstance->sendLocalNotification (notification);
|
||||
|
||||
#else
|
||||
SystemTrayIconComponent systemTrayIcon;
|
||||
|
||||
Image im (Image::ARGB, 128, 128, true);
|
||||
systemTrayIcon.setIconImage (im, im);
|
||||
|
||||
systemTrayIcon.showInfoBubble (notificationTitle, notificationBody);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,710 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/** Singleton class responsible for push notifications functionality. Both remote and
|
||||
local notifications are supported. To get information about notifications,
|
||||
register a listener on your application startup. It is best to register the
|
||||
listener as soon as possible, because your application can be launched from
|
||||
a push notification too.
|
||||
|
||||
To send a local notification create an instance of Notification, fill the necessary
|
||||
fields and call PushNotifications::sendLocalNotification(). When receiving local or
|
||||
remote notifications, inspect the Notification's fields for notification details.
|
||||
Bear in mind that some fields will not be available when receiving a remote
|
||||
notification.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API PushNotifications : private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
#ifndef DOXYGEN
|
||||
JUCE_DECLARE_SINGLETON_INLINE (PushNotifications, false)
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
/** Represents a notification that can be sent or received. */
|
||||
struct Notification
|
||||
{
|
||||
Notification() = default;
|
||||
Notification (const Notification& other);
|
||||
|
||||
/** Checks whether a given notification is correctly configured for a given OS. */
|
||||
bool isValid() const noexcept;
|
||||
|
||||
/** Represents an action on a notification that can be presented as a button or a text input.
|
||||
On Android, each notification has its action specified explicitly, on iOS you configure an
|
||||
allowed set of actions on startup and pack them into categories (see Settings).
|
||||
*/
|
||||
struct Action
|
||||
{
|
||||
/** Controls the appearance of this action. */
|
||||
enum Style
|
||||
{
|
||||
button, /**< Show this action as a button. */
|
||||
text /**< Show this action as a text input field (on Android API 20 or higher is required). */
|
||||
};
|
||||
|
||||
/** @name Common fields */
|
||||
/**@{*/
|
||||
Style style = button;
|
||||
String title; /**< Required. the name of the action displayed to the user. */
|
||||
String textInputPlaceholder; /**< Optional: placeholder text for text input notification.
|
||||
Note that it will be ignored if button style is used. */
|
||||
var parameters; /**< Optional: additional parameters that can be passed. */
|
||||
/**@}*/
|
||||
|
||||
/** @name iOS only fields */
|
||||
/**@{*/
|
||||
String identifier; /**< Required: unique identifier. This should be one of the
|
||||
identifiers set with requestPermissionsWithSettings(). */
|
||||
bool triggerInBackground = false; /**< Whether the app can process the action in background. */
|
||||
bool destructive = false; /**< Whether to display the action as destructive. */
|
||||
String textInputButtonText; /**< Optional: Text displayed on text input notification
|
||||
button.
|
||||
Note that it will be ignored if style is set to Style::button. */
|
||||
/**@}*/
|
||||
|
||||
/** @name Android only fields */
|
||||
/**@{*/
|
||||
String icon; /**< Optional: name of an icon file (without an extension) to be used for
|
||||
this action. This must be the name of one of the image
|
||||
files included into resources when exporting an Android project
|
||||
(see "Extra Android Raw Resources" setting in Projucer).
|
||||
Note that not all Android versions support an icon for an action, though
|
||||
it is recommended to provide it nevertheless. */
|
||||
|
||||
StringArray allowedResponses; /**< Optional: a list of possible answers if the answer set is limited.
|
||||
When left empty, then the user will be able to input any text. */
|
||||
/**@}*/
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @name Common fields */
|
||||
/**@{*/
|
||||
|
||||
String identifier; /**< Required: unique id that can be used to later dismiss the notification
|
||||
(on iOS available from version 10). */
|
||||
|
||||
String title; /**< Required: the title of the notification, usually displayed in the first row. */
|
||||
String body; /**< Required: the content of the notification, usually displayed in the second row. */
|
||||
String subtitle; /**< Optional: additional text, that may be displayed e.g. in the third row or in the header
|
||||
area. Note that on Android, depending on OS version, this may fight for
|
||||
space with other components of the notification, so use this field
|
||||
judiciously. On iOS available from version 10. On Android available from API 16. */
|
||||
|
||||
String groupId; /**< Optional: allows the OS to visually group, collapse, and expand a set of notifications,
|
||||
note that OS may automatically group notifications if no groupId is specified. */
|
||||
|
||||
int badgeNumber = 0; /**< Optional: on platforms that support it, can set a number this notification represents. */
|
||||
URL soundToPlay; /**< Optional: empty when the notification should be silent. When the name is set to
|
||||
"default_os_sound", then a default sound will be used.
|
||||
|
||||
For a custom sound on OSX, set the URL to the name of a sound file (preferably without
|
||||
an extension) and place the sound file directly in bundle's "Resources" directory (you
|
||||
can use "Xcode Resource" tickbox in Projucer to achieve that), i.e. it cannot be in a
|
||||
subdirectory of "Resources" like "Resources/sound". Alternatively, if a sound file
|
||||
cannot be found in bundle's "Resources" directory, the OS may look for the sound in the
|
||||
following paths: "~/Library/Sounds", "/Library/Sounds", "/Network/Library/Sounds",
|
||||
"/System/Library/Sounds".
|
||||
|
||||
For a custom sound on iOS, set the URL to a relative path within your bundle, including
|
||||
file extension. For instance, if your bundle contains "sounds" folder with "my_sound.caf"
|
||||
file, then the URL should be "sounds/my_sound.caf".
|
||||
|
||||
For a custom sound on Android, set URL to the name of a raw resource file
|
||||
(without an extension) that was included when exporting an Android project in
|
||||
Projucer (see "Extra Android Raw Resources" setting). */
|
||||
|
||||
var properties; /**< Optional: collection of additional properties that may be passed as a dictionary. */
|
||||
|
||||
/**@}*/
|
||||
|
||||
//==============================================================================
|
||||
/** @name iOS only fields */
|
||||
/**@{*/
|
||||
|
||||
String category; /**< Required: determines set of actions that will appear (as per setup done
|
||||
in requestPermissionsWithSettings()). */
|
||||
double triggerIntervalSec = 0.; /**< Optional: specifies number of seconds before the notification should trigger. */
|
||||
bool repeat = false; /**< Optional: allows the notification to continuously retrigger after
|
||||
triggerIntervalSec seconds. */
|
||||
|
||||
/**@}*/
|
||||
|
||||
//==============================================================================
|
||||
/** @name Android only fields */
|
||||
/**@{*/
|
||||
|
||||
String icon; /**< Required: name of an icon file (without an extension) to be used for
|
||||
this notification. This must be the name of one of the image
|
||||
files included into resources when exporting an Android project
|
||||
(see "Extra Android Raw Resources" setting in Projucer). */
|
||||
|
||||
String channelId; /**< Required for Android API level 26 or above: specifies notification channel id. Refer to
|
||||
setupChannels(). Ignored on earlier Android versions. */
|
||||
|
||||
Image largeIcon; /**< Optional: an additional large icon displayed in the notification content view. */
|
||||
|
||||
String tickerText; /**< Optional: ticker text used for accessibility services. */
|
||||
|
||||
Array<Action> actions; /**< Optional: actions associated with the notification. Note that the OS may allow only a limited
|
||||
number of actions to be presented, so always present most important actions first.
|
||||
Available from Android API 16 or above. */
|
||||
|
||||
/** Used to represent a progress of some operation. */
|
||||
struct Progress
|
||||
{
|
||||
int max = 0; /**< Max possible value of a progress. A typical usecase is to set max to 100 and increment
|
||||
current's value as percentage complete. */
|
||||
int current = 0; /**< Current progress value, should be from 0 to max. */
|
||||
bool indeterminate = false; /**< If true, then the progress represents a continuing activity indicator with ongoing
|
||||
animation and no numeric value. */
|
||||
};
|
||||
|
||||
Progress progress; /**< Optional: set to default (0, 0, false), to disable progress display. */
|
||||
|
||||
/** Metadata that can be used by the OS to better handle the notification, depending on its priority. */
|
||||
enum Type
|
||||
{
|
||||
unspecified, /**< Category not set. */
|
||||
alarm, /**< Alarm or timer. */
|
||||
call, /**< Incoming voice/video call or similar. */
|
||||
email, /**< Async message like email. */
|
||||
error, /**< Error in background operation or authentication status. */
|
||||
event, /**< Calendar event. */
|
||||
message, /**< Incoming message (sms, instant message etc.). */
|
||||
taskProgress, /**< Progress for a long-running background operation. */
|
||||
promo, /**< Promotion or advertisement. */
|
||||
recommendation, /**< Specific, single thing related recommendation. */
|
||||
reminder, /**< User-scheduled reminder. */
|
||||
service, /**< Running background service. */
|
||||
social, /**< Social network or sharing update. */
|
||||
status, /**< Ongoing information about device or contextual status. */
|
||||
system, /**< System or device status update. */
|
||||
transport /**< Media transport control for playback. */
|
||||
};
|
||||
|
||||
/** Metadata used as a hint to the OS about the priority of the notification. */
|
||||
enum Priority
|
||||
{
|
||||
veryLow = -2,
|
||||
low = -1,
|
||||
medium = 0,
|
||||
high = 1,
|
||||
veryHigh = 2
|
||||
};
|
||||
|
||||
String person; /**< Optional: additional metadata used as a hint to OS that a notification is
|
||||
related to a specific person. Can be useful for instance messaging apps.
|
||||
Available from Android API 21 or above. */
|
||||
|
||||
Type type = unspecified; /**< Optional. Available from Android API 21 or above. */
|
||||
Priority priority = medium; /**< Optional. Available from Android API 16 or above. */
|
||||
|
||||
/** Describes how to show the notification when the screen is locked. Available from Android API 21 or above. */
|
||||
enum LockScreenAppearance
|
||||
{
|
||||
dontShow = -1, /**< The notification is not allowed on the lock screen */
|
||||
showPartially = 0, /**< Only some information is allowed on the lock screen */
|
||||
showCompletely = 1 /**< The entire notification is allowed on the lock screen */
|
||||
};
|
||||
|
||||
LockScreenAppearance lockScreenAppearance = showPartially; /**< Optional. */
|
||||
|
||||
std::unique_ptr<Notification> publicVersion; /**< Optional: if you set lockScreenAppearance to showPartially,
|
||||
then you can provide "public version" of your notification
|
||||
that will be displayed on the lock screen. This way you can
|
||||
control what information is visible when the screen is locked. */
|
||||
|
||||
String groupSortKey; /**< Optional: Used to order notifications within the same group. Available from Android API 20 or above. */
|
||||
bool groupSummary = false; /**< Optional: if true, then this notification will be a group summary of the group set with groupId.
|
||||
Available from Android API 20 or above. */
|
||||
|
||||
Colour accentColour; /**< Optional: sets accent colour. The default colour will be used if accentColour is not set.
|
||||
Available from Android API 21 or above. */
|
||||
Colour ledColour; /**< Optional: Sets the led colour. The hardware will do its best to approximate the colour.
|
||||
The default colour will be used if ledColour is not set. */
|
||||
|
||||
/** Allows to control the time the device's led is on and off. */
|
||||
struct LedBlinkPattern
|
||||
{
|
||||
int msToBeOn = 0; /**< The led will be on for the given number of milliseconds, after which it will turn off. */
|
||||
int msToBeOff = 0; /**< The led will be off for the given number of milliseconds, after which it will turn on. */
|
||||
};
|
||||
|
||||
LedBlinkPattern ledBlinkPattern; /**< Optional. */
|
||||
|
||||
Array<int> vibrationPattern; /**< Optional: sets the vibration pattern in milliseconds. The first value indicates how long
|
||||
to wait until vibration starts. The second value indicates how long to vibrate. The third
|
||||
value will say how long to not vibrate and so on. For instance, if the pattern is:
|
||||
1000, 2000, 3000, 4000 - then one second after receiving a notification the device will
|
||||
vibrate for two seconds, followed by 3 seconds of no vibration and finally, 4 seconds of
|
||||
vibration. */
|
||||
|
||||
bool shouldAutoCancel = true; /**< Optional: If true, the notification will be automatically cancelled when a user clicks it in the panel. */
|
||||
|
||||
bool localOnly = true; /**< Optional: whether or not the notification should bridge to other devices.
|
||||
Available from Android API 20 or above. */
|
||||
|
||||
bool ongoing = false; /**< Optional: If true, then it cannot be dismissed by the user and it must be dismissed manually.
|
||||
Typically used for ongoing background tasks that the user is actively engaged with. To
|
||||
dismiss such notification, you need to call removeDeliveredNotification() or
|
||||
removeAllDeliveredNotifications(). */
|
||||
|
||||
bool alertOnlyOnce = false; /**< Optional: Set this flag if you would only like the sound, vibrate and ticker to be played if the notification
|
||||
is not already showing. */
|
||||
|
||||
/** Controls timestamp visibility and format. */
|
||||
enum TimestampVisibility
|
||||
{
|
||||
off, /**< Do not show timestamp. */
|
||||
normal, /**< Show normal timestamp. */
|
||||
chronometer, /**< Show chronometer as a stopwatch. Available from Android API 16 or above. */
|
||||
countDownChronometer /**< Set the chronometer to count down instead of counting up. Available from Android API 24 or above.*/
|
||||
};
|
||||
|
||||
TimestampVisibility timestampVisibility = normal; /**< Optional. */
|
||||
|
||||
/** Controls badge icon type to use if a notification is shown as a badge. Available from Android API 26 or above. */
|
||||
enum BadgeIconType
|
||||
{
|
||||
none,
|
||||
small,
|
||||
large
|
||||
};
|
||||
|
||||
BadgeIconType badgeIconType = large;
|
||||
|
||||
/** Controls sound and vibration behaviour for group notifications. Available from Android API 26 or above. */
|
||||
enum GroupAlertBehaviour
|
||||
{
|
||||
alertAll, /**< both child notifications and group notifications should produce sound and vibration. */
|
||||
AlertSummary, /**< all child notifications in the group should have no sound nor vibration, even
|
||||
if corresponding notification channel has sounds and vibrations enabled. */
|
||||
AlertChildren /**< summary notifications in the group should have no sound nor vibration, even if
|
||||
corresponding notification channel has sounds and vibrations enabled. */
|
||||
};
|
||||
|
||||
GroupAlertBehaviour groupAlertBehaviour = alertAll;
|
||||
|
||||
int timeoutAfterMs = 0; /**< specifies a duration in milliseconds, after which the notification should be
|
||||
cancelled, if it is not already cancelled. Available from Android API 26 or above. */
|
||||
/**@}*/
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Describes settings we want to use for current device. Note that at the
|
||||
moment this is only used on iOS and partially on OSX.
|
||||
|
||||
On OSX only allow* flags are used and they control remote notifications only.
|
||||
To control sound, alert and badge settings for local notifications on OSX,
|
||||
use Notifications settings in System Preferences.
|
||||
|
||||
To setup push notifications for current device, provide permissions required,
|
||||
as well as register categories of notifications you want to support. Each
|
||||
category needs to have a unique identifier and it can optionally have multiple
|
||||
actions. Each action also needs to have a unique identifier. The example setup
|
||||
may look as follows:
|
||||
|
||||
@code
|
||||
|
||||
using Action = PushNotifications::Settings::Action;
|
||||
using Category = PushNotifications::Settings::Category;
|
||||
|
||||
Action okAction;
|
||||
okAction.identifier = "okAction";
|
||||
okAction.title = "OK!";
|
||||
okAction.style = Action::button;
|
||||
okAction.triggerInBackground = true;
|
||||
|
||||
Action cancelAction;
|
||||
cancelAction.identifier = "cancelAction";
|
||||
cancelAction.title = "Cancel";
|
||||
cancelAction.style = Action::button;
|
||||
cancelAction.triggerInBackground = true;
|
||||
cancelAction.destructive = true;
|
||||
|
||||
Action textAction;
|
||||
textAction.identifier = "textAction";
|
||||
textAction.title = "Enter text";
|
||||
textAction.style = Action::text;
|
||||
textAction.triggerInBackground = true;
|
||||
textAction.destructive = false;
|
||||
textAction.textInputButtonText = "Ok";
|
||||
textAction.textInputPlaceholder = "Enter text...";
|
||||
|
||||
Category okCategory;
|
||||
okCategory.identifier = "okCategory";
|
||||
okCategory.actions = { okAction };
|
||||
|
||||
Category okCancelCategory;
|
||||
okCancelCategory.identifier = "okCancelCategory";
|
||||
okCancelCategory.actions = { okAction, cancelAction };
|
||||
|
||||
Category textCategory;
|
||||
textCategory.identifier = "textCategory";
|
||||
textCategory.actions = { textAction };
|
||||
textCategory.sendDismissAction = true;
|
||||
|
||||
PushNotifications::Settings settings;
|
||||
settings.allowAlert = true;
|
||||
settings.allowBadge = true;
|
||||
settings.allowSound = true;
|
||||
settings.categories = { okCategory, okCancelCategory, textCategory };
|
||||
|
||||
@endcode
|
||||
*/
|
||||
struct Settings
|
||||
{
|
||||
using Action = Notification::Action;
|
||||
|
||||
/** Describes a category of a notification. Each category has a unique identifier
|
||||
and a list of associated actions.
|
||||
Note that the OS may allow only a limited number of actions to be presented, so
|
||||
always present most important actions first.
|
||||
*/
|
||||
struct Category
|
||||
{
|
||||
String identifier; /**< unique identifier */
|
||||
Array<Action> actions; /**< optional list of actions within this category */
|
||||
bool sendDismissAction = false; /**< whether dismiss action will be sent to the app */
|
||||
};
|
||||
|
||||
bool allowSound = false; /**< whether the app should play a sound upon notification */
|
||||
bool allowAlert = false; /**< whether the app should present an alert upon notification */
|
||||
bool allowBadge = false; /**< whether the app may badge its icon upon notification */
|
||||
Array<Category> categories; /**< list of categories the app wants to support */
|
||||
};
|
||||
|
||||
/** Initialises push notifications on current device with the settings provided.
|
||||
Call this on your application startup and on iOS the first time the application starts,
|
||||
a user will be presented with a permission request dialog to give push notifications permission.
|
||||
Once a user responds, Listener::notificationSettingsReceived() will be called so that
|
||||
you can check what permissions where actually granted. The listener callback will be called
|
||||
on each subsequent startup too (provided you called requestPermissionsWithSettings() on previous
|
||||
application run). This way you can check what are current push notifications permissions.
|
||||
|
||||
Note that settings are currently only used on iOS. When calling on other platforms, Settings
|
||||
with no categories and all allow* flags set to true will be received in
|
||||
Listener::notificationSettingsReceived().
|
||||
|
||||
You can also call requestSettingsUsed() to explicitly ask for current settings.
|
||||
*/
|
||||
void requestPermissionsWithSettings (const Settings& settings);
|
||||
|
||||
/** Sends an asynchronous request to retrieve current settings that are currently in use.
|
||||
These can be exactly the same as used in requestPermissionsWithSettings(), but depending
|
||||
on user's subsequent changes in OS settings, the actual current settings may be
|
||||
different (e.g. user might have later decided to disable sounds).
|
||||
|
||||
Note that settings are currently only used on iOS and partially on OSX.
|
||||
|
||||
On OSX, only allow* flags are used and they refer to remote notifications only. For
|
||||
local notifications, refer to System Preferences.
|
||||
|
||||
When calling this function on other platforms, Settings with no categories and all allow*
|
||||
flags set to true will be received in Listener::notificationSettingsReceived().
|
||||
*/
|
||||
void requestSettingsUsed();
|
||||
|
||||
//==============================================================================
|
||||
/** Android API level 26 or higher only: Represents notification channel through which
|
||||
notifications will be sent. Starting from Android API level 26, you should call setupChannels()
|
||||
at the start of your application, before posting any notifications. Then, when sending notifications,
|
||||
assign a channel to each created notification.
|
||||
*/
|
||||
struct Channel
|
||||
{
|
||||
String identifier; /**< Required: Unique channel identifier. */
|
||||
String name; /**< Required: User facing name of the channel. */
|
||||
|
||||
/** Controls how interruptive the notification posted on this channel are. */
|
||||
enum Importance
|
||||
{
|
||||
none,
|
||||
min,
|
||||
low,
|
||||
normal,
|
||||
high,
|
||||
max
|
||||
};
|
||||
|
||||
Importance importance = normal; /**< Required. */
|
||||
Notification::LockScreenAppearance lockScreenAppearance = Notification::showPartially; /**< Optional. */
|
||||
|
||||
String description; /**< Optional: user visible description of the channel. */
|
||||
String groupId; /**< Required: group this channel belongs to (see ChannelGroup). */
|
||||
Colour ledColour; /**< Optional: sets the led colour for notifications in this channel. */
|
||||
bool bypassDoNotDisturb = false; /**< Optional: true if notifications in this channel can bypass do not disturb setting. */
|
||||
bool canShowBadge = false; /**< Optional: true if notifications in this channel can show badges in a Launcher application. */
|
||||
bool enableLights = false; /**< Optional: true if notifications in this channel should show lights (subject to hardware support). */
|
||||
bool enableVibration = false; /**< Optional: true if notifications in this channel should trigger vibrations. */
|
||||
|
||||
URL soundToPlay; /**< Optional: sound to play in this channel. See Notification::soundToPlay for more info. */
|
||||
Array<int> vibrationPattern; /**< Optional: vibration pattern for this channel. See Notification::vibrationPattern for more info. */
|
||||
};
|
||||
|
||||
/** Android API level 26 or higher only: represents a channel group. This allows for
|
||||
visual grouping of corresponding channels in notification settings presented to the user.
|
||||
At least one channel group has to be specified before notifications can be sent.
|
||||
*/
|
||||
struct ChannelGroup
|
||||
{
|
||||
String identifier; /**< Required: Unique channel group identifier. */
|
||||
String name; /**< Required: User visible name of the channel group. */
|
||||
};
|
||||
|
||||
/** Android API level 26 or higher only: configures notification channel groups and channels to be
|
||||
used in the app. These have to be setup before notifications can be sent on Android API
|
||||
level 26 or higher.
|
||||
*/
|
||||
void setupChannels (const Array<ChannelGroup>& groups, const Array<Channel>& channels);
|
||||
|
||||
//==============================================================================
|
||||
/** iOS only: sends an asynchronous request to retrieve a list of notifications that were
|
||||
scheduled and not yet delivered.
|
||||
|
||||
When the list is retrieved, Listener::pendingLocalNotificationsListReceived() will be called.
|
||||
*/
|
||||
void getPendingLocalNotifications() const;
|
||||
|
||||
/** Unschedules a pending local notification with a given identifier. */
|
||||
void removePendingLocalNotification (const String& identifier);
|
||||
|
||||
/** Unschedules all pending local notifications. iOS only. */
|
||||
void removeAllPendingLocalNotifications();
|
||||
|
||||
//==============================================================================
|
||||
/** Checks whether notifications are enabled for given application.
|
||||
On iOS and OSX this will always return true, use requestSettingsUsed() instead.
|
||||
*/
|
||||
bool areNotificationsEnabled() const;
|
||||
|
||||
/** On iOS as well as on Android, sends a local notification.
|
||||
This will refresh an existing notification if the same identifier is used as in
|
||||
a notification that was already sent and not yet responded by a user.
|
||||
*/
|
||||
void sendLocalNotification (const Notification& notification);
|
||||
|
||||
/** Sends a request for a list of notifications delivered. Such notifications are visible in the
|
||||
notification area on the device and they are still waiting for user action/response.
|
||||
When the request is finished Listener::deliveredNotificationsListReceived() will be called.
|
||||
|
||||
On iOS, iOS version 10 or higher is required. On Android, API level 18 or higher is required.
|
||||
For unsupported platforms, Listener::deliveredNotificationsListReceived() will return an empty array.
|
||||
*/
|
||||
void getDeliveredNotifications() const;
|
||||
|
||||
/** Removes a previously delivered notification. This can be useful for instance when the
|
||||
information in the notification becomes obsolete.
|
||||
*/
|
||||
void removeDeliveredNotification (const String& identifier);
|
||||
|
||||
/** Removes all notifications that were delivered. */
|
||||
void removeAllDeliveredNotifications();
|
||||
|
||||
//==============================================================================
|
||||
/** Retrieves current device token. Note, it is not a good idea to cache this token
|
||||
because it may change in the meantime. Always call this method to get the current
|
||||
token value.
|
||||
*/
|
||||
String getDeviceToken() const;
|
||||
|
||||
/** Android only: allows to subscribe to messages from a specific topic.
|
||||
So you could for instance subscribe this device to all "sports" topic messages
|
||||
to receive any remote notifications that have "sports" topic set.
|
||||
Refer to Firebase documentation for how to send topic messages.
|
||||
*/
|
||||
void subscribeToTopic (const String& topic);
|
||||
|
||||
/** Android only: allows to remove a topic subscription that was previously added with
|
||||
subscribeToTopic().
|
||||
*/
|
||||
void unsubscribeFromTopic (const String& topic);
|
||||
|
||||
/** Android only: sends an upstream message to your app server. The server must implement
|
||||
XMPP Connection Server protocol (refer to Firebase documentation).
|
||||
|
||||
@param serverSenderId Represents the sender. Consult your Firebase project
|
||||
settings to retrieve the sender id.
|
||||
|
||||
@param collapseKey Remote messages with the same collapse key that were not
|
||||
yet delivered will be collapsed into one, with the
|
||||
newest message replacing all the previous ones.
|
||||
Note that there may be a limit of maximum collapse keys
|
||||
used at the same time and beyond the limit (refer to
|
||||
Firebase documentation) it is not guaranteed which keys
|
||||
will be in use by the server.
|
||||
|
||||
@param messageId A unique message ID. Used in error callbacks and debugging.
|
||||
|
||||
@param messageType Message type.
|
||||
|
||||
@param timeToLive TTL in seconds. If 0, the message sending will be attempted
|
||||
immediately and it will be dropped if the device is not
|
||||
connected. Otherwise, the message will be queued for the
|
||||
period specified.
|
||||
|
||||
@param additionalData Collection of key-value pairs to be used as an additional
|
||||
data for the message.
|
||||
*/
|
||||
void sendUpstreamMessage (const String& serverSenderId,
|
||||
const String& collapseKey,
|
||||
const String& messageId,
|
||||
const String& messageType,
|
||||
int timeToLive,
|
||||
const StringPairArray& additionalData);
|
||||
|
||||
//==============================================================================
|
||||
/** Register a listener (ideally on application startup) to receive information about
|
||||
notifications received and any callbacks to async functions called.
|
||||
*/
|
||||
struct Listener
|
||||
{
|
||||
virtual ~Listener() = default;
|
||||
|
||||
/** This callback will be called after you call requestSettingsUsed() or
|
||||
requestPermissionsWithSettings().
|
||||
|
||||
Note that settings are currently only used on iOS. When called on other platforms, Settings
|
||||
with no categories and all allow flags set to true will be received in
|
||||
Listener::notificationSettingsReceived().
|
||||
*/
|
||||
virtual void notificationSettingsReceived (const Settings& settings);
|
||||
|
||||
/** Called when the list of pending notifications, requested by calling
|
||||
getPendingLocalNotifications() is returned.
|
||||
*/
|
||||
virtual void pendingLocalNotificationsListReceived (const Array<Notification>& notifications);
|
||||
|
||||
/** This can be called in multiple different situations, depending on the OS and the situation.
|
||||
|
||||
This will be called when a user presses on a notification
|
||||
|
||||
Note: On Android, if remote notification was received while the app was in the background and
|
||||
then user pressed on it, the notification object received in this callback will contain only
|
||||
"properties" member set. Hence, if you want to know what was the notification title, content
|
||||
etc, you need to set them as additional properties, so that you will be able to restore them
|
||||
from "properties" dictionary.
|
||||
|
||||
Note you can receive this callback on startup, if the application was launched from a notification.
|
||||
*/
|
||||
virtual void handleNotification (bool isLocalNotification, const Notification& notification);
|
||||
|
||||
/** This can be called when a user performs some action on the notification such as
|
||||
pressing on an action button or responding with a text input.
|
||||
Note that pressing on a notification area, i.e. not on an action button is not considered
|
||||
to be an action, and hence receivedNotification() will be called in that case.
|
||||
|
||||
Note you can receive this callback on startup, if the application was launched from a notification's action.
|
||||
|
||||
@param isLocalNotification If the notification is local
|
||||
@param notification The notification
|
||||
@param actionIdentifier A String identifying the action
|
||||
@param optionalResponse Text response a user inputs for notifications with a text input.
|
||||
Empty for notifications without a text input option.
|
||||
|
||||
*/
|
||||
virtual void handleNotificationAction (bool isLocalNotification,
|
||||
const Notification& notification,
|
||||
const String& actionIdentifier,
|
||||
const String& optionalResponse);
|
||||
|
||||
/** For iOS10 and Android, this can be also called when a user dismissed the notification before
|
||||
responding to it.
|
||||
*/
|
||||
virtual void localNotificationDismissedByUser (const Notification& notification);
|
||||
|
||||
/** Called after getDeliveredNotifications() request is fulfilled. Returns notifications
|
||||
that are visible in the notification area on the device and that are still waiting
|
||||
for a user action/response.
|
||||
|
||||
On iOS, iOS version 10 or higher is required. On Android, API level 18 or higher is required.
|
||||
For unsupported platforms, an empty array will be returned.
|
||||
*/
|
||||
virtual void deliveredNotificationsListReceived (const Array<Notification>& notifications);
|
||||
|
||||
/** Called whenever a token gets refreshed. You should monitor any token updates, because
|
||||
only the last token that is assigned to device is valid and can be used.
|
||||
*/
|
||||
virtual void deviceTokenRefreshed (const String& token);
|
||||
|
||||
/** Called when Firebase Cloud Messaging server deletes pending messages. This can happen when
|
||||
1) too many messages were sent to the server (hint: use collapsible messages).
|
||||
2) the devices hasn't been online in a long time (refer to Firebase documentation for
|
||||
the maximum time a message can be stored on FCM before expiring).
|
||||
*/
|
||||
virtual void remoteNotificationsDeleted();
|
||||
|
||||
/** Called when an upstream message sent with PushNotifications::sendUpstreamMessage() has been
|
||||
sent successfully.
|
||||
Bear in mind that in may take several minutes or more to receive this callback.
|
||||
*/
|
||||
virtual void upstreamMessageSent (const String& messageId);
|
||||
|
||||
/** Called when there was an error sending an upstream message with
|
||||
PushNotifications::sendUpstreamMessage().
|
||||
Bear in mind that in may take several minutes or more to receive this callback.
|
||||
*/
|
||||
virtual void upstreamMessageSendingError (const String& messageId, const String& error);
|
||||
};
|
||||
|
||||
void addListener (Listener* l);
|
||||
void removeListener (Listener* l);
|
||||
|
||||
private:
|
||||
PushNotifications();
|
||||
~PushNotifications() override;
|
||||
|
||||
ListenerList<PushNotifications::Listener> listeners;
|
||||
|
||||
#if JUCE_ANDROID
|
||||
friend bool juce_handleNotificationIntent (void*);
|
||||
|
||||
friend struct JuceFirebaseInstanceIdService;
|
||||
friend struct JuceFirebaseMessagingService;
|
||||
#endif
|
||||
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> pimpl;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
RecentlyOpenedFilesList::RecentlyOpenedFilesList()
|
||||
: maxNumberOfItems (10)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void RecentlyOpenedFilesList::setMaxNumberOfItems (const int newMaxNumber)
|
||||
{
|
||||
maxNumberOfItems = jmax (1, newMaxNumber);
|
||||
|
||||
files.removeRange (maxNumberOfItems, getNumFiles());
|
||||
}
|
||||
|
||||
int RecentlyOpenedFilesList::getNumFiles() const
|
||||
{
|
||||
return files.size();
|
||||
}
|
||||
|
||||
File RecentlyOpenedFilesList::getFile (const int index) const
|
||||
{
|
||||
return File (files [index]);
|
||||
}
|
||||
|
||||
void RecentlyOpenedFilesList::clear()
|
||||
{
|
||||
files.clear();
|
||||
}
|
||||
|
||||
void RecentlyOpenedFilesList::addFile (const File& file)
|
||||
{
|
||||
removeFile (file);
|
||||
files.insert (0, file.getFullPathName());
|
||||
|
||||
setMaxNumberOfItems (maxNumberOfItems);
|
||||
}
|
||||
|
||||
void RecentlyOpenedFilesList::removeFile (const File& file)
|
||||
{
|
||||
files.removeString (file.getFullPathName());
|
||||
}
|
||||
|
||||
void RecentlyOpenedFilesList::removeNonExistentFiles()
|
||||
{
|
||||
for (int i = getNumFiles(); --i >= 0;)
|
||||
if (! getFile (i).exists())
|
||||
files.remove (i);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int RecentlyOpenedFilesList::createPopupMenuItems (PopupMenu& menuToAddTo,
|
||||
const int baseItemId,
|
||||
const bool showFullPaths,
|
||||
const bool dontAddNonExistentFiles,
|
||||
const File** filesToAvoid)
|
||||
{
|
||||
int num = 0;
|
||||
|
||||
for (int i = 0; i < getNumFiles(); ++i)
|
||||
{
|
||||
const File f (getFile (i));
|
||||
|
||||
if ((! dontAddNonExistentFiles) || f.exists())
|
||||
{
|
||||
bool needsAvoiding = false;
|
||||
|
||||
if (filesToAvoid != nullptr)
|
||||
{
|
||||
for (const File** avoid = filesToAvoid; *avoid != nullptr; ++avoid)
|
||||
{
|
||||
if (f == **avoid)
|
||||
{
|
||||
needsAvoiding = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! needsAvoiding)
|
||||
{
|
||||
menuToAddTo.addItem (baseItemId + i,
|
||||
showFullPaths ? f.getFullPathName()
|
||||
: f.getFileName());
|
||||
++num;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
String RecentlyOpenedFilesList::toString() const
|
||||
{
|
||||
return files.joinIntoString ("\n");
|
||||
}
|
||||
|
||||
void RecentlyOpenedFilesList::restoreFromString (const String& stringifiedVersion)
|
||||
{
|
||||
clear();
|
||||
files.addLines (stringifiedVersion);
|
||||
|
||||
setMaxNumberOfItems (maxNumberOfItems);
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
void RecentlyOpenedFilesList::registerRecentFileNatively ([[maybe_unused]] const File& file)
|
||||
{
|
||||
#if JUCE_MAC
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
[[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: createNSURLFromFile (file)];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void RecentlyOpenedFilesList::forgetRecentFileNatively ([[maybe_unused]] const File& file)
|
||||
{
|
||||
#if JUCE_MAC
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
// for some reason, OSX doesn't provide a method to just remove a single file
|
||||
// from the recent list, so we clear them all and add them back excluding
|
||||
// the specified file
|
||||
|
||||
auto sharedDocController = [NSDocumentController sharedDocumentController];
|
||||
auto recentDocumentURLs = [sharedDocController recentDocumentURLs];
|
||||
|
||||
[sharedDocController clearRecentDocuments: nil];
|
||||
|
||||
auto* nsFile = createNSURLFromFile (file);
|
||||
|
||||
auto reverseEnumerator = [recentDocumentURLs reverseObjectEnumerator];
|
||||
|
||||
for (NSURL* url : reverseEnumerator)
|
||||
if (! [url isEqual:nsFile])
|
||||
[sharedDocController noteNewRecentDocumentURL:url];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void RecentlyOpenedFilesList::clearRecentFilesNatively()
|
||||
{
|
||||
#if JUCE_MAC
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
[[NSDocumentController sharedDocumentController] clearRecentDocuments: nil];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Manages a set of files for use as a list of recently-opened documents.
|
||||
|
||||
This is a handy class for holding your list of recently-opened documents, with
|
||||
helpful methods for things like purging any non-existent files, automatically
|
||||
adding them to a menu, and making persistence easy.
|
||||
|
||||
@see File, FileBasedDocument
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API RecentlyOpenedFilesList
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty list.
|
||||
*/
|
||||
RecentlyOpenedFilesList();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets a limit for the number of files that will be stored in the list.
|
||||
|
||||
When addFile() is called, then if there is no more space in the list, the
|
||||
least-recently added file will be dropped.
|
||||
|
||||
@see getMaxNumberOfItems
|
||||
*/
|
||||
void setMaxNumberOfItems (int newMaxNumber);
|
||||
|
||||
/** Returns the number of items that this list will store.
|
||||
@see setMaxNumberOfItems
|
||||
*/
|
||||
int getMaxNumberOfItems() const noexcept { return maxNumberOfItems; }
|
||||
|
||||
/** Returns the number of files in the list.
|
||||
|
||||
The most recently added file is always at index 0.
|
||||
*/
|
||||
int getNumFiles() const;
|
||||
|
||||
/** Returns one of the files in the list.
|
||||
|
||||
The most recently added file is always at index 0.
|
||||
*/
|
||||
File getFile (int index) const;
|
||||
|
||||
/** Returns an array of all the absolute pathnames in the list.
|
||||
*/
|
||||
const StringArray& getAllFilenames() const noexcept { return files; }
|
||||
|
||||
/** Clears all the files from the list. */
|
||||
void clear();
|
||||
|
||||
/** Adds a file to the list.
|
||||
|
||||
The file will be added at index 0. If this file is already in the list, it will
|
||||
be moved up to index 0, but a file can only appear once in the list.
|
||||
|
||||
If the list already contains the maximum number of items that is permitted, the
|
||||
least-recently added file will be dropped from the end.
|
||||
*/
|
||||
void addFile (const File& file);
|
||||
|
||||
/** Removes a file from the list. */
|
||||
void removeFile (const File& file);
|
||||
|
||||
/** Checks each of the files in the list, removing any that don't exist.
|
||||
|
||||
You might want to call this after reloading a list of files, or before putting them
|
||||
on a menu.
|
||||
*/
|
||||
void removeNonExistentFiles();
|
||||
|
||||
/** Tells the OS to add a file to the OS-managed list of recent documents for this app.
|
||||
|
||||
Not all OSes maintain a list of recent files for an application, so this
|
||||
function will have no effect on some OSes. Currently it's just implemented for OSX.
|
||||
*/
|
||||
static void registerRecentFileNatively (const File& file);
|
||||
|
||||
/** Tells the OS to remove a file from the OS-managed list of recent documents for this app.
|
||||
|
||||
Not all OSes maintain a list of recent files for an application, so this
|
||||
function will have no effect on some OSes. Currently it's just implemented for OSX.
|
||||
*/
|
||||
static void forgetRecentFileNatively (const File& file);
|
||||
|
||||
/** Tells the OS to clear the OS-managed list of recent documents for this app.
|
||||
|
||||
Not all OSes maintain a list of recent files for an application, so this
|
||||
function will have no effect on some OSes. Currently it's just implemented for OSX.
|
||||
*/
|
||||
static void clearRecentFilesNatively();
|
||||
|
||||
//==============================================================================
|
||||
/** Adds entries to a menu, representing each of the files in the list.
|
||||
|
||||
This is handy for creating an "open recent file..." menu in your app. The
|
||||
menu items are numbered consecutively starting with the baseItemId value,
|
||||
and can either be added as complete pathnames, or just the last part of the
|
||||
filename.
|
||||
|
||||
If dontAddNonExistentFiles is true, then each file will be checked and only those
|
||||
that exist will be added.
|
||||
|
||||
If filesToAvoid is not a nullptr, then it is considered to be a zero-terminated array
|
||||
of pointers to file objects. Any files that appear in this list will not be added to
|
||||
the menu - the reason for this is that you might have a number of files already open,
|
||||
so might not want these to be shown in the menu.
|
||||
|
||||
It returns the number of items that were added.
|
||||
*/
|
||||
int createPopupMenuItems (PopupMenu& menuToAddItemsTo,
|
||||
int baseItemId,
|
||||
bool showFullPaths,
|
||||
bool dontAddNonExistentFiles,
|
||||
const File** filesToAvoid = nullptr);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a string that encapsulates all the files in the list.
|
||||
|
||||
The string that is returned can later be passed into restoreFromString() in
|
||||
order to recreate the list. This is handy for persisting your list, e.g. in
|
||||
a PropertiesFile object.
|
||||
|
||||
@see restoreFromString
|
||||
*/
|
||||
String toString() const;
|
||||
|
||||
/** Restores the list from a previously stringified version of the list.
|
||||
|
||||
Pass in a stringified version created with toString() in order to persist/restore
|
||||
your list.
|
||||
|
||||
@see toString
|
||||
*/
|
||||
void restoreFromString (const String& stringifiedVersion);
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
StringArray files;
|
||||
int maxNumberOfItems;
|
||||
|
||||
JUCE_LEAK_DETECTOR (RecentlyOpenedFilesList)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
SplashScreen::SplashScreen (const String& title, const Image& image, bool useDropShadow)
|
||||
: Component (title),
|
||||
backgroundImage (image),
|
||||
clickCountToDelete (0)
|
||||
{
|
||||
// You must supply a valid image here!
|
||||
jassert (backgroundImage.isValid());
|
||||
|
||||
setOpaque (! backgroundImage.hasAlphaChannel());
|
||||
|
||||
#if JUCE_IOS || JUCE_ANDROID
|
||||
const bool useFullScreen = true;
|
||||
#else
|
||||
const bool useFullScreen = false;
|
||||
#endif
|
||||
|
||||
makeVisible (image.getWidth(), image.getHeight(), useDropShadow, useFullScreen);
|
||||
}
|
||||
|
||||
SplashScreen::SplashScreen (const String& title, int width, int height, bool useDropShadow)
|
||||
: Component (title),
|
||||
clickCountToDelete (0)
|
||||
{
|
||||
makeVisible (width, height, useDropShadow, false);
|
||||
}
|
||||
|
||||
void SplashScreen::makeVisible (int w, int h, bool useDropShadow, bool fullscreen)
|
||||
{
|
||||
clickCountToDelete = Desktop::getInstance().getMouseButtonClickCounter();
|
||||
creationTime = Time::getCurrentTime();
|
||||
|
||||
const Rectangle<int> screenSize = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea;
|
||||
const int width = (fullscreen ? screenSize.getWidth() : w);
|
||||
const int height = (fullscreen ? screenSize.getHeight() : h);
|
||||
|
||||
setAlwaysOnTop (true);
|
||||
setVisible (true);
|
||||
centreWithSize (width, height);
|
||||
addToDesktop (useDropShadow ? ComponentPeer::windowHasDropShadow : 0);
|
||||
|
||||
if (fullscreen)
|
||||
getPeer()->setFullScreen (true);
|
||||
|
||||
toFront (false);
|
||||
}
|
||||
|
||||
SplashScreen::~SplashScreen() {}
|
||||
|
||||
void SplashScreen::deleteAfterDelay (RelativeTime timeout, bool removeOnMouseClick)
|
||||
{
|
||||
// Note that this method must be safe to call from non-GUI threads
|
||||
if (! removeOnMouseClick)
|
||||
clickCountToDelete = std::numeric_limits<int>::max();
|
||||
|
||||
minimumVisibleTime = timeout;
|
||||
|
||||
startTimer (50);
|
||||
}
|
||||
|
||||
void SplashScreen::paint (Graphics& g)
|
||||
{
|
||||
g.setOpacity (1.0f);
|
||||
g.drawImage (backgroundImage, getLocalBounds().toFloat(), RectanglePlacement (RectanglePlacement::fillDestination));
|
||||
}
|
||||
|
||||
void SplashScreen::timerCallback()
|
||||
{
|
||||
if (Time::getCurrentTime() > creationTime + minimumVisibleTime
|
||||
|| Desktop::getInstance().getMouseButtonClickCounter() > clickCountToDelete)
|
||||
delete this;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/** A component for showing a splash screen while your app starts up.
|
||||
|
||||
This will automatically position itself, and can be told to delete itself after
|
||||
being on-screen for a minimum length of time.
|
||||
|
||||
To use it, just create one of these in your JUCEApplicationBase::initialise() method,
|
||||
and when your initialisation tasks have finished running, call its deleteAfterDelay()
|
||||
method to make it automatically get rid of itself.
|
||||
|
||||
Note that although you could call deleteAfterDelay() as soon as you create the
|
||||
SplashScreen object, if you've got a long initialisation procedure, you probably
|
||||
don't want the splash to time-out and disappear before the initialisation has
|
||||
finished, which is why it makes sense to not call this method until the end of
|
||||
your init tasks.
|
||||
|
||||
E.g. @code
|
||||
|
||||
void MyApp::initialise (const String& commandLine)
|
||||
{
|
||||
splash = new SplashScreen ("Welcome to my app!",
|
||||
ImageFileFormat::loadFrom (File ("/foobar/splash.jpg")),
|
||||
true);
|
||||
|
||||
// now kick off your initialisation work on some kind of thread or task, and
|
||||
launchBackgroundInitialisationThread();
|
||||
}
|
||||
|
||||
void MyApp::myInitialisationWorkFinished()
|
||||
{
|
||||
// ..assuming this is some kind of callback method that is triggered when
|
||||
// your background initialisation threads have finished, and it's time to open
|
||||
// your main window, etc..
|
||||
|
||||
splash->deleteAfterDelay (RelativeTime::seconds (4), false);
|
||||
|
||||
...etc...
|
||||
}
|
||||
|
||||
@endcode
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API SplashScreen : public Component,
|
||||
private Timer,
|
||||
private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a SplashScreen object.
|
||||
|
||||
When called, the constructor will position the SplashScreen in the centre of the
|
||||
display, and after the time specified, it will automatically delete itself.
|
||||
|
||||
Bear in mind that if you call this during your JUCEApplicationBase::initialise()
|
||||
method and then block the message thread by performing some kind of task, then
|
||||
obviously neither your splash screen nor any other GUI will appear until you
|
||||
allow the message thread to resume and do its work. So if you have time-consuming
|
||||
tasks to do during startup, use a background thread for them.
|
||||
|
||||
After creating one of these (or your subclass of it), you should do your app's
|
||||
initialisation work, and then call the deleteAfterDelay() method to tell this object
|
||||
to delete itself after the user has had chance to get a good look at it.
|
||||
|
||||
If you're writing a custom splash screen class, there's another protected constructor
|
||||
that your subclass can call, which doesn't take an image.
|
||||
|
||||
@param title the name to give the component
|
||||
@param backgroundImage an image to draw on the component. The component's size
|
||||
will be set to the size of this image, and if the image is
|
||||
semi-transparent, the component will be made non-opaque
|
||||
@param useDropShadow if true, the window will have a drop shadow
|
||||
|
||||
*/
|
||||
SplashScreen (const String& title,
|
||||
const Image& backgroundImage,
|
||||
bool useDropShadow);
|
||||
|
||||
/** Destructor. */
|
||||
~SplashScreen() override;
|
||||
|
||||
/** Tells the component to auto-delete itself after a timeout period, or when the
|
||||
mouse is clicked.
|
||||
|
||||
You should call this after finishing your app's initialisation work.
|
||||
|
||||
Note that although you could call deleteAfterDelay() as soon as you create the
|
||||
SplashScreen object, if you've got a long initialisation procedure, you probably
|
||||
don't want the splash to time-out and disappear before your initialisation has
|
||||
finished, which is why it makes sense to not call this method and start the
|
||||
self-delete timer until you're ready.
|
||||
|
||||
It's safe to call this method from a non-GUI thread as long as there's no danger that
|
||||
the object may be being deleted at the same time.
|
||||
|
||||
@param minimumTotalTimeToDisplayFor how long the splash screen should stay visible for.
|
||||
Note that this time is measured from the construction-time of this
|
||||
object, not from the time that the deleteAfterDelay() method is
|
||||
called, so if you call this method after a long initialisation
|
||||
period, it may be deleted without any further delay.
|
||||
@param removeOnMouseClick if true, the window will be deleted as soon as the user clicks
|
||||
the mouse (anywhere)
|
||||
*/
|
||||
void deleteAfterDelay (RelativeTime minimumTotalTimeToDisplayFor,
|
||||
bool removeOnMouseClick);
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** This constructor is for use by custom sub-classes that don't want to provide an image. */
|
||||
SplashScreen (const String& title, int width, int height, bool useDropShadow);
|
||||
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Image backgroundImage;
|
||||
Time creationTime;
|
||||
RelativeTime minimumVisibleTime;
|
||||
int clickCountToDelete;
|
||||
|
||||
void timerCallback() override;
|
||||
void makeVisible (int w, int h, bool shadow, bool fullscreen);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SplashScreen)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD || JUCE_MAC
|
||||
|
||||
SystemTrayIconComponent::SystemTrayIconComponent()
|
||||
{
|
||||
addToDesktop (0);
|
||||
}
|
||||
|
||||
SystemTrayIconComponent::~SystemTrayIconComponent()
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD || JUCE_MAC || DOXYGEN
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This component sits in the taskbar tray as a small icon.
|
||||
|
||||
(NB: The exact behaviour of this class will differ between OSes, and it
|
||||
isn't fully implemented for all OSes)
|
||||
|
||||
To use it, just create one of these components, but don't attempt to make it
|
||||
visible, add it to a parent, or put it on the desktop.
|
||||
|
||||
You can then call setIconImage() to create an icon for it in the taskbar.
|
||||
|
||||
To change the icon's tooltip, you can use setIconTooltip().
|
||||
|
||||
To respond to mouse-events, you can override the normal mouseDown(),
|
||||
mouseUp(), mouseDoubleClick() and mouseMove() methods, and although the x, y
|
||||
position will not be valid, you can use this to respond to clicks. Traditionally
|
||||
you'd use a left-click to show your application's window, and a right-click
|
||||
to show a pop-up menu.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API SystemTrayIconComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Constructor. */
|
||||
SystemTrayIconComponent();
|
||||
|
||||
/** Destructor. */
|
||||
~SystemTrayIconComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the image shown in the taskbar.
|
||||
|
||||
On Windows and Linux a full colour Image is used as an icon.
|
||||
On macOS a template image is used, where all non-transparent regions will be
|
||||
rendered in a monochrome colour selected dynamically by the operating system.
|
||||
|
||||
@param colourImage An colour image to use as an icon on Windows and Linux
|
||||
@param templateImage A template image to use as an icon on macOS
|
||||
*/
|
||||
void setIconImage (const Image& colourImage, const Image& templateImage);
|
||||
|
||||
/** Changes the icon's tooltip (if the current OS supports this). */
|
||||
void setIconTooltip (const String& tooltip);
|
||||
|
||||
/** Highlights the icon (if the current OS supports this). */
|
||||
void setHighlighted (bool);
|
||||
|
||||
/** Shows a floating text bubble pointing to the icon (if the current OS supports this). */
|
||||
void showInfoBubble (const String& title, const String& content);
|
||||
|
||||
/** Hides the icon's floating text bubble (if the current OS supports this). */
|
||||
void hideInfoBubble();
|
||||
|
||||
/** Returns the raw handle to whatever kind of internal OS structure is
|
||||
involved in showing this icon.
|
||||
@see ComponentPeer::getNativeHandle()
|
||||
*/
|
||||
void* getNativeHandle() const;
|
||||
|
||||
#if JUCE_LINUX || JUCE_BSD
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
#endif
|
||||
|
||||
#if JUCE_MAC
|
||||
/** Shows a menu attached to the OSX menu bar icon. */
|
||||
void showDropdownMenu (const PopupMenu& menu);
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
JUCE_PUBLIC_IN_DLL_BUILD (class Pimpl)
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
|
||||
[[deprecated ("The new setIconImage function signature requires different images for macOS and the other platforms.")]]
|
||||
void setIconImage (const Image& newImage);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SystemTrayIconComponent)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,730 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_WEB_BROWSER || DOXYGEN
|
||||
|
||||
bool WebBrowserComponent::pageAboutToLoad ([[maybe_unused]] const String& newURL) { return true; }
|
||||
void WebBrowserComponent::pageFinishedLoading ([[maybe_unused]] const String& url) {}
|
||||
bool WebBrowserComponent::pageLoadHadNetworkError ([[maybe_unused]] const String& errorInfo) { return true; }
|
||||
void WebBrowserComponent::windowCloseRequest() {}
|
||||
void WebBrowserComponent::newWindowAttemptingToLoad ([[maybe_unused]] const String& newURL) {}
|
||||
|
||||
// At least this much code has to be injected as user script, since the native backend functions
|
||||
// rely on the presence of the `window.__JUCE__.backend.emitByBackend()` function. The rest can be
|
||||
// optionally imported as JS.
|
||||
static constexpr const char* lowLevelIntegrationsScript = R"(
|
||||
if (
|
||||
typeof window.__JUCE__ !== "undefined" &&
|
||||
typeof window.__JUCE__.getAndroidUserScripts !== "undefined" &&
|
||||
typeof window.inAndroidUserScriptEval === "undefined"
|
||||
) {
|
||||
window.inAndroidUserScriptEval = true;
|
||||
eval(window.__JUCE__.getAndroidUserScripts());
|
||||
delete window.inAndroidUserScriptEval;
|
||||
}
|
||||
|
||||
{
|
||||
if (typeof window.__JUCE__ === "undefined") {
|
||||
console.warn(
|
||||
"The 'window.__JUCE__' object is undefined." +
|
||||
" Native integration features will not work." +
|
||||
" Defining a placeholder 'window.__JUCE__' object."
|
||||
);
|
||||
|
||||
window.__JUCE__ = {
|
||||
postMessage: function () {},
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof window.__JUCE__.initialisationData === "undefined") {
|
||||
window.__JUCE__.initialisationData = {
|
||||
__juce__platform: [],
|
||||
__juce__functions: [],
|
||||
__juce__registeredGlobalEventIds: [],
|
||||
__juce__sliders: [],
|
||||
__juce__toggles: [],
|
||||
__juce__comboBoxes: [],
|
||||
};
|
||||
}
|
||||
|
||||
class ListenerList {
|
||||
constructor() {
|
||||
this.listeners = new Map();
|
||||
this.listenerId = 0;
|
||||
}
|
||||
|
||||
addListener(fn) {
|
||||
const newListenerId = this.listenerId++;
|
||||
this.listeners.set(newListenerId, fn);
|
||||
return newListenerId;
|
||||
}
|
||||
|
||||
removeListener(id) {
|
||||
if (this.listeners.has(id)) {
|
||||
this.listeners.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
callListeners(payload) {
|
||||
for (const [, value] of this.listeners) {
|
||||
value(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EventListenerList {
|
||||
constructor() {
|
||||
this.eventListeners = new Map();
|
||||
}
|
||||
|
||||
addEventListener(eventId, fn) {
|
||||
if (!this.eventListeners.has(eventId))
|
||||
this.eventListeners.set(eventId, new ListenerList());
|
||||
|
||||
const id = this.eventListeners.get(eventId).addListener(fn);
|
||||
|
||||
return [eventId, id];
|
||||
}
|
||||
|
||||
removeEventListener([eventId, id]) {
|
||||
if (this.eventListeners.has(eventId)) {
|
||||
this.eventListeners.get(eventId).removeListener(id);
|
||||
}
|
||||
}
|
||||
|
||||
emitEvent(eventId, object) {
|
||||
if (this.eventListeners.has(eventId))
|
||||
this.eventListeners.get(eventId).callListeners(object);
|
||||
}
|
||||
}
|
||||
|
||||
class Backend {
|
||||
constructor() {
|
||||
this.listeners = new EventListenerList();
|
||||
}
|
||||
|
||||
addEventListener(eventId, fn) {
|
||||
return this.listeners.addEventListener(eventId, fn);
|
||||
}
|
||||
|
||||
removeEventListener([eventId, id]) {
|
||||
this.listeners.removeEventListener([eventId, id]);
|
||||
}
|
||||
|
||||
emitEvent(eventId, object) {
|
||||
window.__JUCE__.postMessage(
|
||||
JSON.stringify({ eventId: eventId, payload: object })
|
||||
);
|
||||
}
|
||||
|
||||
emitByBackend(eventId, object) {
|
||||
this.listeners.emitEvent(eventId, JSON.parse(object));
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window.__JUCE__.backend === "undefined")
|
||||
window.__JUCE__.backend = new Backend();
|
||||
}
|
||||
)";
|
||||
|
||||
static void evaluationHandler (WebBrowserComponent::EvaluationResult r)
|
||||
{
|
||||
if (r.getResult() == nullptr)
|
||||
{
|
||||
// The unsupported return type means a successful Javascript evaluation that yielded a
|
||||
// result that cannot be translated and returned to native code such as a Promise.
|
||||
jassert (r.getError()->type == WebBrowserComponent::EvaluationResult::Error::Type::unsupportedReturnType);
|
||||
DBG (r.getError()->message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static StringArray getUserScriptsForInitialisationData (const std::vector<std::pair<String, var>>& data)
|
||||
{
|
||||
std::map<String, StringArray> sortedData;
|
||||
|
||||
for (const auto& [key, value] : data)
|
||||
sortedData[key].add (JSON::toString (value));
|
||||
|
||||
StringArray result;
|
||||
|
||||
for (const auto& [key, values] : sortedData)
|
||||
result.add ("window.__JUCE__.initialisationData." + key + " = ["
|
||||
+ values.joinIntoString (",") + "];");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static auto getCommaSeparatedList (const T& mapOfStringable)
|
||||
{
|
||||
StringArray keys;
|
||||
|
||||
for (auto it : mapOfStringable)
|
||||
keys.add (it.first.toString().quoted());
|
||||
|
||||
return keys.joinIntoString (",");
|
||||
}
|
||||
|
||||
struct NativeEvents
|
||||
{
|
||||
NativeEvents() = delete;
|
||||
|
||||
struct NativeEvent
|
||||
{
|
||||
String eventId;
|
||||
var payload;
|
||||
|
||||
static constexpr std::optional<int> marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename Item>
|
||||
static void serialise (Archive& archive, Item& item)
|
||||
{
|
||||
archive (named ("eventId", item.eventId),
|
||||
named ("payload", item.payload));
|
||||
}
|
||||
};
|
||||
|
||||
struct Invoke
|
||||
{
|
||||
String name;
|
||||
var params;
|
||||
int64 resultId;
|
||||
|
||||
static constexpr std::optional<int> marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename Item>
|
||||
static void serialise (Archive& archive, Item& item)
|
||||
{
|
||||
archive (named ("name", item.name),
|
||||
named ("params", item.params),
|
||||
named ("resultId", item.resultId));
|
||||
}
|
||||
|
||||
static inline const Identifier eventId { "__juce__invoke" };
|
||||
static inline const Identifier completeId { "__juce__complete" };
|
||||
};
|
||||
};
|
||||
|
||||
class NativeFunctionsProvider : public OptionsBuilder<WebBrowserComponent::Options>,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
explicit NativeFunctionsProvider (WebBrowserComponent& ownerIn)
|
||||
: owner (ownerIn)
|
||||
{
|
||||
}
|
||||
|
||||
WebBrowserComponent::Options buildOptions (const WebBrowserComponent::Options& initialOptions) override
|
||||
{
|
||||
nativeFunctions = initialOptions.getNativeFunctions();
|
||||
|
||||
if (nativeFunctions.empty())
|
||||
return initialOptions;
|
||||
|
||||
auto options = initialOptions.withNativeIntegrationEnabled()
|
||||
.withEventListener (NativeEvents::Invoke::eventId,
|
||||
[this] (const auto& object)
|
||||
{
|
||||
handleNativeFunctionCall (object);
|
||||
});
|
||||
|
||||
for (auto it : nativeFunctions)
|
||||
options = options.withInitialisationData ("__juce__functions", it.first.toString());
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private:
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
const auto popFront = [&] (auto&& callback)
|
||||
{
|
||||
functionCompletionsMutex.lock();
|
||||
ErasedScopeGuard unlocker { [&] { functionCompletionsMutex.unlock(); } };
|
||||
|
||||
if (! functionCompletions.empty())
|
||||
{
|
||||
auto completion = functionCompletions.front();
|
||||
functionCompletions.pop_front();
|
||||
unlocker.reset();
|
||||
callback (completion);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
while (popFront ([&] (const auto& c) { emitCompletionEvent (c.first, c.second); }))
|
||||
;
|
||||
}
|
||||
|
||||
void handleNativeFunctionCall (const var& object)
|
||||
{
|
||||
const auto invocation = FromVar::convert<NativeEvents::Invoke> (object);
|
||||
|
||||
if (! invocation.has_value())
|
||||
{
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = nativeFunctions.find (invocation->name);
|
||||
|
||||
if (it == nativeFunctions.end())
|
||||
{
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
jassert (invocation->params.isArray());
|
||||
|
||||
it->second (*invocation->params.getArray(),
|
||||
[this, resultId = invocation->resultId] (auto result)
|
||||
{
|
||||
completeNativeFunctionCall (resultId, result);
|
||||
});
|
||||
}
|
||||
|
||||
void completeNativeFunctionCall (int64 resultId, const var& object)
|
||||
{
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
{
|
||||
emitCompletionEvent (resultId, object);
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::lock_guard lock { functionCompletionsMutex };
|
||||
functionCompletions.push_back (std::make_pair (resultId, object));
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void emitCompletionEvent (int64 resultId, const var& object)
|
||||
{
|
||||
DynamicObject::Ptr eventObject { new DynamicObject() };
|
||||
eventObject->setProperty ("promiseId", resultId);
|
||||
eventObject->setProperty ("result", object);
|
||||
|
||||
jassert (owner.isVisible());
|
||||
owner.emitEventIfBrowserIsVisible (NativeEvents::Invoke::completeId, eventObject.get());
|
||||
}
|
||||
|
||||
WebBrowserComponent& owner;
|
||||
std::map<Identifier, WebBrowserComponent::NativeFunction> nativeFunctions;
|
||||
std::deque<std::pair<int64, var>> functionCompletions;
|
||||
std::mutex functionCompletionsMutex;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (NativeFunctionsProvider)
|
||||
JUCE_DECLARE_NON_MOVEABLE (NativeFunctionsProvider)
|
||||
};
|
||||
|
||||
class NativeEventListeners
|
||||
{
|
||||
public:
|
||||
void addListener (const Identifier& eventId, std::function<void (const var&)> handler)
|
||||
{
|
||||
listeners.push_back (std::move (handler));
|
||||
listenerMap[eventId].add (&listeners.back());
|
||||
}
|
||||
|
||||
void emit (const Identifier& eventId, const var& object)
|
||||
{
|
||||
if (const auto& it = listenerMap.find (eventId); it != listenerMap.end())
|
||||
it->second.call ([&object] (auto& l) { l (object); });
|
||||
}
|
||||
|
||||
private:
|
||||
std::list<WebBrowserComponent::NativeEventListener> listeners;
|
||||
|
||||
std::map<Identifier, ListenerList<WebBrowserComponent::NativeEventListener>> listenerMap;
|
||||
};
|
||||
|
||||
class WebBrowserComponent::Impl
|
||||
{
|
||||
public:
|
||||
Impl (WebBrowserComponent& ownerIn, const Options& optionsIn)
|
||||
: owner (ownerIn),
|
||||
options ([&]
|
||||
{
|
||||
makeFunctionsProviderIfNecessary (nativeFunctionsProvider, *this, optionsIn);
|
||||
|
||||
if (nativeFunctionsProvider.has_value())
|
||||
return getOptions (optionsIn).withOptionsFrom (*nativeFunctionsProvider);
|
||||
|
||||
return getOptions (optionsIn);
|
||||
}())
|
||||
{
|
||||
auto userScripts = options.getUserScripts();
|
||||
|
||||
// Adding user scripts in reverse order of depending on each other. The most depended on
|
||||
// comes last.
|
||||
for (const auto& eventListener : options.getEventListeners())
|
||||
{
|
||||
userScripts.insert (0,
|
||||
"window.__JUCE__.initialisationData.__juce__registeredGlobalEventIds = ["
|
||||
+ getCommaSeparatedList (options.getEventListeners()) + "];");
|
||||
|
||||
addPermanentEventListener (eventListener.first, eventListener.second);
|
||||
}
|
||||
|
||||
for (const auto& script : getUserScriptsForInitialisationData (options.getInitialisationData()))
|
||||
userScripts.insert (0, script);
|
||||
|
||||
userScripts.insert (0, lowLevelIntegrationsScript);
|
||||
|
||||
platform = createAndInitPlatformDependentPart (*this, options, userScripts);
|
||||
}
|
||||
|
||||
void emitEvent (const Identifier& eventId, const var& object)
|
||||
{
|
||||
// The object parameter is serialised into a string and used as a parameter to a Javascript
|
||||
// function call. During this JS parameter substitution, control character escape sequences
|
||||
// would be interpreted as the control characters themselves. So we need to escape anything
|
||||
// that was escaped.
|
||||
//
|
||||
// We also need to escape the ' character since we use this to delimit the parameter string
|
||||
// to emitByBackend.
|
||||
const auto objectAsString = JSON::toString (object, true);
|
||||
const auto escaped = objectAsString.replace ("\\", "\\\\").replace ("'", "\\'");
|
||||
|
||||
evaluateJavascript ("window.__JUCE__.backend.emitByBackend(" + eventId.toString().quoted() + ", "
|
||||
+ escaped.quoted ('\'')
|
||||
+ ");", evaluationHandler);
|
||||
}
|
||||
|
||||
void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData)
|
||||
{
|
||||
platform->goToURL (url, headers, postData);
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
platform->stop();
|
||||
}
|
||||
|
||||
void goBack()
|
||||
{
|
||||
platform->goBack();
|
||||
|
||||
owner.lastURL.clear();
|
||||
owner.blankPageShown = false;
|
||||
}
|
||||
|
||||
void goForward()
|
||||
{
|
||||
owner.lastURL.clear();
|
||||
|
||||
platform->goForward();
|
||||
}
|
||||
|
||||
void refresh()
|
||||
{
|
||||
platform->refresh();
|
||||
}
|
||||
|
||||
void evaluateJavascript (const String& script, EvaluationCallback callback)
|
||||
{
|
||||
platform->evaluateJavascript (script, std::move (callback));
|
||||
}
|
||||
|
||||
void setSize (int width, int height)
|
||||
{
|
||||
platform->setWebViewSize (width, height);
|
||||
}
|
||||
|
||||
void checkWindowAssociation()
|
||||
{
|
||||
platform->checkWindowAssociation();
|
||||
}
|
||||
|
||||
void fallbackPaint (Graphics& webBrowserComponentContext)
|
||||
{
|
||||
platform->fallbackPaint (webBrowserComponentContext);
|
||||
}
|
||||
|
||||
void focusGainedWithDirection (FocusChangeType type, FocusChangeDirection dir)
|
||||
{
|
||||
platform->focusGainedWithDirection (type, dir);
|
||||
}
|
||||
|
||||
struct Platform;
|
||||
|
||||
private:
|
||||
class PlatformInterface
|
||||
{
|
||||
public:
|
||||
PlatformInterface() = default;
|
||||
virtual ~PlatformInterface() = default;
|
||||
|
||||
virtual void goToURL (const String&, const StringArray*, const MemoryBlock*) = 0;
|
||||
virtual void goBack() = 0;
|
||||
virtual void goForward() = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual void refresh() = 0;
|
||||
virtual void evaluateJavascript (const String&, WebBrowserComponent::EvaluationCallback) = 0;
|
||||
virtual void setWebViewSize (int, int) = 0;
|
||||
virtual void checkWindowAssociation() = 0;
|
||||
|
||||
virtual void focusGainedWithDirection (FocusChangeType, FocusChangeDirection) {}
|
||||
virtual void fallbackPaint (Graphics&) {}
|
||||
};
|
||||
|
||||
static void makeFunctionsProviderIfNecessary (std::optional<NativeFunctionsProvider>& provider,
|
||||
WebBrowserComponent::Impl& impl,
|
||||
const WebBrowserComponent::Options& options)
|
||||
{
|
||||
if (! options.getNativeFunctions().empty())
|
||||
provider.emplace (impl.owner);
|
||||
}
|
||||
|
||||
static Options getOptions (const Options& optionsIn)
|
||||
{
|
||||
const auto os = SystemStats::getOperatingSystemType();
|
||||
|
||||
const auto platformString = [&]
|
||||
{
|
||||
using OsType = SystemStats::OperatingSystemType;
|
||||
|
||||
if ((os & OsType::MacOSX) != 0)
|
||||
return "macos";
|
||||
|
||||
if ((os & OsType::iOS) != 0)
|
||||
return "ios";
|
||||
|
||||
if ((os & OsType::Windows) != 0)
|
||||
return "windows";
|
||||
|
||||
if ((os & OsType::Android) != 0)
|
||||
return "android";
|
||||
|
||||
if ((os & OsType::Linux) != 0)
|
||||
return "linux";
|
||||
|
||||
return "";
|
||||
}();
|
||||
|
||||
return optionsIn.withInitialisationData ("__juce__platform", platformString);
|
||||
}
|
||||
|
||||
void addPermanentEventListener (const Identifier& eventId, NativeEventListener listener)
|
||||
{
|
||||
nativeEventListeners.addListener (eventId, std::move (listener));
|
||||
}
|
||||
|
||||
std::optional<Resource> handleResourceRequest (const String& url)
|
||||
{
|
||||
if (resourceProvider != nullptr)
|
||||
return resourceProvider (url);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void handleNativeEvent (const var& message)
|
||||
{
|
||||
const auto event = FromVar::convert<NativeEvents::NativeEvent> (message);
|
||||
|
||||
if (! event.has_value())
|
||||
{
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
nativeEventListeners.emit (event->eventId, event->payload);
|
||||
}
|
||||
|
||||
static std::unique_ptr<PlatformInterface> createAndInitPlatformDependentPart (WebBrowserComponent::Impl&,
|
||||
const Options&,
|
||||
const StringArray&);
|
||||
|
||||
WebBrowserComponent& owner;
|
||||
std::optional<NativeFunctionsProvider> nativeFunctionsProvider;
|
||||
Options options;
|
||||
ResourceProvider resourceProvider { options.getResourceProvider() };
|
||||
NativeEventListeners nativeEventListeners;
|
||||
|
||||
std::unique_ptr<PlatformInterface> platform;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
WebBrowserComponent::WebBrowserComponent (const Options& options)
|
||||
: impl (std::make_unique<Impl> (*this, options)),
|
||||
unloadPageWhenHidden (! options.keepsPageLoadedWhenBrowserIsHidden())
|
||||
{
|
||||
setOpaque (true);
|
||||
|
||||
#if JUCE_LINUX
|
||||
ignoreUnused (blankPageShown);
|
||||
ignoreUnused (unloadPageWhenHidden);
|
||||
#endif
|
||||
|
||||
for (const auto& l : options.getLifetimeListeners())
|
||||
lifetimeListeners.add (l);
|
||||
|
||||
lifetimeListeners.call (&WebViewLifetimeListener::webViewConstructed, this);
|
||||
}
|
||||
|
||||
WebBrowserComponent::~WebBrowserComponent()
|
||||
{
|
||||
lifetimeListeners.call (&WebViewLifetimeListener::webViewDestructed, this);
|
||||
}
|
||||
|
||||
void WebBrowserComponent::emitEventIfBrowserIsVisible (const Identifier& eventId, const var& object)
|
||||
{
|
||||
if (isVisible())
|
||||
impl->emitEvent (eventId, object);
|
||||
}
|
||||
|
||||
const String& WebBrowserComponent::getResourceProviderRoot()
|
||||
{
|
||||
const auto os = SystemStats::getOperatingSystemType();
|
||||
|
||||
using OsType = SystemStats::OperatingSystemType;
|
||||
|
||||
if ( (os & OsType::MacOSX) != 0
|
||||
|| (os & OsType::iOS) != 0
|
||||
|| (os & OsType::Linux) != 0)
|
||||
{
|
||||
static String backendUrl { "juce://juce.backend/" };
|
||||
return backendUrl;
|
||||
}
|
||||
|
||||
if ( (os & OsType::Windows) != 0
|
||||
|| (os & OsType::Android) != 0)
|
||||
{
|
||||
static String backendUrl { "https://juce.backend/" };
|
||||
return backendUrl;
|
||||
}
|
||||
|
||||
static String emptyUrl { "" };
|
||||
return emptyUrl;
|
||||
}
|
||||
|
||||
void WebBrowserComponent::goToURL (const String& url,
|
||||
const StringArray* headers,
|
||||
const MemoryBlock* postData)
|
||||
{
|
||||
lastURL = url;
|
||||
|
||||
if (headers != nullptr)
|
||||
lastHeaders = *headers;
|
||||
else
|
||||
lastHeaders.clear();
|
||||
|
||||
if (postData != nullptr)
|
||||
lastPostData = *postData;
|
||||
else
|
||||
lastPostData.reset();
|
||||
|
||||
impl->goToURL (url, headers, postData);
|
||||
|
||||
blankPageShown = false;
|
||||
}
|
||||
|
||||
void WebBrowserComponent::stop()
|
||||
{
|
||||
impl->stop();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::goBack()
|
||||
{
|
||||
impl->goBack();
|
||||
lastURL.clear();
|
||||
blankPageShown = false;
|
||||
}
|
||||
|
||||
void WebBrowserComponent::goForward()
|
||||
{
|
||||
lastURL.clear();
|
||||
impl->goForward();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::refresh()
|
||||
{
|
||||
impl->refresh();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::paint (Graphics& g)
|
||||
{
|
||||
impl->fallbackPaint (g);
|
||||
}
|
||||
|
||||
void WebBrowserComponent::parentHierarchyChanged()
|
||||
{
|
||||
impl->checkWindowAssociation();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::visibilityChanged()
|
||||
{
|
||||
impl->checkWindowAssociation();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::resized()
|
||||
{
|
||||
impl->setSize (getWidth(), getHeight());
|
||||
}
|
||||
|
||||
void WebBrowserComponent::reloadLastURL()
|
||||
{
|
||||
const auto ptrOrNullIfEmpty = [] (auto& value)
|
||||
{
|
||||
return ! value.isEmpty() ? &value : nullptr;
|
||||
};
|
||||
|
||||
if (lastURL.isNotEmpty())
|
||||
{
|
||||
goToURL (lastURL, ptrOrNullIfEmpty (lastHeaders), ptrOrNullIfEmpty (lastPostData));
|
||||
lastURL.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowserComponent::evaluateJavascript (const String& script, EvaluationCallback callback)
|
||||
{
|
||||
impl->evaluateJavascript (script, std::move (callback));
|
||||
}
|
||||
|
||||
void WebBrowserComponent::focusGainedWithDirection (FocusChangeType type,
|
||||
FocusChangeDirection direction)
|
||||
{
|
||||
impl->focusGainedWithDirection (type, direction);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,663 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_WEB_BROWSER || DOXYGEN
|
||||
|
||||
#if JUCE_MAC || JUCE_IOS || (JUCE_WINDOWS && JUCE_USE_WIN_WEBVIEW2) || JUCE_ANDROID || JUCE_LINUX
|
||||
#define JUCE_WEB_BROWSER_RESOURCE_PROVIDER_AVAILABLE 1
|
||||
#else
|
||||
#define JUCE_WEB_BROWSER_RESOURCE_PROVIDER_AVAILABLE 0
|
||||
#endif
|
||||
|
||||
class WebBrowserComponent;
|
||||
|
||||
/** Type for a listener registered with
|
||||
WebBrowserComponent::Options::withWebViewLifetimeListener. This can be useful for
|
||||
types using the WebBrowserComponent::Options::withOptionsFrom function as they have to be
|
||||
constructed before the WebBrowserComponent.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API WebViewLifetimeListener
|
||||
{
|
||||
public:
|
||||
virtual ~WebViewLifetimeListener() = default;
|
||||
|
||||
/** Called from the WebBrowserComponent constructor. */
|
||||
virtual void webViewConstructed (WebBrowserComponent*) = 0;
|
||||
|
||||
/** Called from the WebBrowserComponent destructor. */
|
||||
virtual void webViewDestructed (WebBrowserComponent*) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that displays an embedded web browser.
|
||||
|
||||
The browser itself will be platform-dependent. On Mac and iOS it will be
|
||||
WebKit, on Android it will be Chrome, and on Linux it will be WebKit.
|
||||
|
||||
The default engine on Windows will be IE, but if JUCE_USE_WIN_WEBVIEW2=1 or
|
||||
JUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKING=1 is defined, then passing the
|
||||
WebBrowserComponent::Options::Backend::webview2 value to the constructor will
|
||||
attempt to use the Chrome based Edge WebView, and fall back to IE in case of
|
||||
failure. CMake builds also need to specify the NEEDS_WEBVIEW2 option when
|
||||
creating a JUCE based target.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API WebBrowserComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Type for a listener registered with Options::withNativeEventListener. */
|
||||
using NativeEventListener = std::function<void (var)>;
|
||||
|
||||
/** Type for the completion passed as the second parameter of NativeFunction. Can be called
|
||||
from any thread.
|
||||
*/
|
||||
using NativeFunctionCompletion = std::function<void (var)>;
|
||||
|
||||
/** Type for functions registered with Options::withNativeFunction. The first parameter is an
|
||||
Array object containing the arguments of the Javascript function invocation.
|
||||
|
||||
The second parameter is the result that completes the Promise returned by the Javascript
|
||||
function call. It can be called from any thread.
|
||||
*/
|
||||
using NativeFunction = std::function<void (const Array<var>&, NativeFunctionCompletion)>;
|
||||
|
||||
/** A resource returned by a ResourceProvider.
|
||||
|
||||
@see Options::withResourceProvider
|
||||
*/
|
||||
struct Resource
|
||||
{
|
||||
std::vector<std::byte> data;
|
||||
String mimeType;
|
||||
};
|
||||
|
||||
/** The type used in Options::withResourceProvider.
|
||||
*/
|
||||
using ResourceProvider = std::function<std::optional<Resource> (const String&)>;
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Options to configure WebBrowserComponent.
|
||||
*/
|
||||
class JUCE_API Options
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
enum class Backend
|
||||
{
|
||||
/**
|
||||
Default web browser backend. WebKit will be used on macOS, gtk-webkit2 on Linux and internet
|
||||
explorer on Windows. On Windows, the default may change to webview2 in the fututre.
|
||||
*/
|
||||
defaultBackend,
|
||||
|
||||
/**
|
||||
Use Internet Explorer as the backend on Windows. By default, IE will use an ancient version
|
||||
of IE. To change this behaviour, you either need to add the following html element into your page's
|
||||
head section:
|
||||
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
|
||||
or you need to change windows registry values for your application. More information on the latter
|
||||
can be found here:
|
||||
|
||||
https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/general-info/ee330730(v=vs.85)?redirectedfrom=MSDN#browser-emulation
|
||||
*/
|
||||
ie,
|
||||
|
||||
/**
|
||||
Use the chromium based WebView2 engine on Windows
|
||||
*/
|
||||
webview2
|
||||
};
|
||||
|
||||
/**
|
||||
Use a particular backend to create the WebViewBrowserComponent. JUCE will silently
|
||||
fallback to the default backend if the selected backend is not supported. To check
|
||||
if a specific backend is supported on your platform or not, use
|
||||
WebBrowserComponent::areOptionsSupported.
|
||||
*/
|
||||
[[nodiscard]] Options withBackend (Backend backend) const { return withMember (*this, &Options::browserBackend, backend); }
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Tell JUCE to keep the web page alive when the WebBrowserComponent is not visible.
|
||||
By default, JUCE will replace the current page with a blank page - this can be
|
||||
handy to stop the browser using resources in the background when it's not
|
||||
actually being used.
|
||||
*/
|
||||
[[nodiscard]] Options withKeepPageLoadedWhenBrowserIsHidden() const { return withMember (*this, &Options::keepPageLoadedWhenBrowserIsHidden, true); }
|
||||
|
||||
/**
|
||||
Use a specific user agent string when requesting web pages.
|
||||
*/
|
||||
[[nodiscard]] Options withUserAgent (String ua) const { return withMember (*this, &Options::userAgent, std::move (ua)); }
|
||||
|
||||
//==============================================================================
|
||||
/** Options specific to the WebView2 backend. These options will be ignored
|
||||
if another backend is used.
|
||||
*/
|
||||
class WinWebView2
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Sets a custom location for the WebView2Loader.dll that is not a part of the
|
||||
standard system DLL search paths.
|
||||
*/
|
||||
[[nodiscard]] WinWebView2 withDLLLocation (const File& location) const { return withMember (*this, &WinWebView2::dllLocation, location); }
|
||||
|
||||
/** Sets a non-default location for storing user data for the browser instance.
|
||||
|
||||
In plugin projects you may find it necessary to use this option and specify a
|
||||
location such as File::SpecialLocationType::tempDirectory. Otherwise WebView2
|
||||
may function incorrectly due to being denied access to the default user data
|
||||
location.
|
||||
*/
|
||||
[[nodiscard]] WinWebView2 withUserDataFolder (const File& folder) const { return withMember (*this, &WinWebView2::userDataFolder, folder); }
|
||||
|
||||
/** If this is set, the status bar usually displayed in the lower-left of the webview
|
||||
will be disabled.
|
||||
*/
|
||||
[[nodiscard]] WinWebView2 withStatusBarDisabled() const { return withMember (*this, &WinWebView2::disableStatusBar, true); }
|
||||
|
||||
/** If this is set, a blank page will be displayed on error instead of the default
|
||||
built-in error page.
|
||||
*/
|
||||
[[nodiscard]] WinWebView2 withBuiltInErrorPageDisabled() const { return withMember (*this, &WinWebView2::disableBuiltInErrorPage, true); }
|
||||
|
||||
/** Sets the background colour that WebView2 renders underneath all web content.
|
||||
|
||||
This colour must either be fully opaque or fully transparent.
|
||||
*/
|
||||
[[nodiscard]] WinWebView2 withBackgroundColour (const Colour& colour) const
|
||||
{
|
||||
// the background colour must be either fully opaque or transparent!
|
||||
jassert (colour.isOpaque() || colour.isTransparent());
|
||||
|
||||
return withMember (*this, &WinWebView2::backgroundColour, colour);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
File getDLLLocation() const { return dllLocation; }
|
||||
File getUserDataFolder() const { return userDataFolder; }
|
||||
bool getIsStatusBarDisabled() const noexcept { return disableStatusBar; }
|
||||
bool getIsBuiltInErrorPageDisabled() const noexcept { return disableBuiltInErrorPage; }
|
||||
Colour getBackgroundColour() const { return backgroundColour; }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
File dllLocation, userDataFolder;
|
||||
bool disableStatusBar = false, disableBuiltInErrorPage = false;
|
||||
Colour backgroundColour;
|
||||
};
|
||||
|
||||
/** Options specific to the WkWebView backend used on Apple systems. These options will be
|
||||
ignored on non-Apple platforms.
|
||||
*/
|
||||
class AppleWkWebView
|
||||
{
|
||||
public:
|
||||
/** Specifies whether the WebView is allowed to access siblings of files specified with
|
||||
the file:// URL scheme.
|
||||
|
||||
Allowing this is a potential security vulnerability if you don't have full control
|
||||
over the file that you are opening.
|
||||
*/
|
||||
[[nodiscard]] AppleWkWebView withAllowAccessToEnclosingDirectory (bool x) const
|
||||
{
|
||||
return withMember (*this, &AppleWkWebView::allowAccessToEnclosingDirectory, x);
|
||||
}
|
||||
|
||||
/** If this options is specified, the underlying WebView will return NO from its
|
||||
acceptsFirstMouse method.
|
||||
|
||||
This disables the click-through behaviour, meaning that clicking a previously
|
||||
unfocused application window only makes the window focused, but will not pass on the
|
||||
click to whichever control inside the WebView is under the mouse.
|
||||
*/
|
||||
[[nodiscard]] AppleWkWebView withDisabledAcceptsFirstMouse() const
|
||||
{
|
||||
return withMember (*this, &AppleWkWebView::acceptsFirstMouse, false);
|
||||
}
|
||||
|
||||
auto getAllowAccessToEnclosingDirectory() const { return allowAccessToEnclosingDirectory; }
|
||||
auto getAcceptsFirstMouse() const { return acceptsFirstMouse; }
|
||||
|
||||
private:
|
||||
bool allowAccessToEnclosingDirectory = false;
|
||||
bool acceptsFirstMouse = true;
|
||||
};
|
||||
|
||||
/** Specifies options that apply to the Windows implementation when the WebView2 feature is
|
||||
enabled.
|
||||
|
||||
@see withBackend
|
||||
*/
|
||||
[[nodiscard]] Options withWinWebView2Options (const WinWebView2& winWebView2Options) const
|
||||
{
|
||||
return withMember (*this, &Options::winWebView2, winWebView2Options);
|
||||
}
|
||||
|
||||
/** Specifies options that influence the WebBrowserComponent's behaviour on Apple systems.
|
||||
*/
|
||||
[[nodiscard]] Options withAppleWkWebViewOptions (const AppleWkWebView& appleWkWebViewOptions) const
|
||||
{
|
||||
return withMember (*this, &Options::appleWkWebView, appleWkWebViewOptions);
|
||||
}
|
||||
|
||||
/** Enables native integration features for the code running inside the WebBrowserComponent.
|
||||
|
||||
This injects data and function objects under `window.__JUCE__.backend` through which
|
||||
scripts running in the WebBrowserComponent can send events to the backend and call
|
||||
registered native functions.
|
||||
|
||||
You should only enable native integrations if you have full control over the content
|
||||
loaded into the component. Navigating to 3rd party websites with these integrations
|
||||
enabled may expose the application and the computer to security risks.
|
||||
|
||||
@see withNativeFunction, withEventListener
|
||||
*/
|
||||
[[nodiscard]] Options withNativeIntegrationEnabled (bool enabled = true) const
|
||||
{
|
||||
return withMember (*this, &Options::enableNativeIntegration, enabled);
|
||||
}
|
||||
|
||||
/** Registers a NativeFunction under the given name.
|
||||
|
||||
To call this function from the frontend, you can import the JUCE frontend helper module
|
||||
or issue a call to the low-level frontend API.
|
||||
|
||||
The callback is always called on the message thread.
|
||||
|
||||
@code
|
||||
import { getNativeFunction } from "./juce";
|
||||
|
||||
function someJavascriptFunction() {
|
||||
const myBackendFunction = getNativeFunction("myBackendFunction");
|
||||
myBackendFunction (1, 2, "some string");
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
[[nodiscard]] Options withNativeFunction (const Identifier& name, NativeFunction callback) const
|
||||
{
|
||||
auto copy = *this;
|
||||
jassert (copy.nativeFunctions.count (name) == 0);
|
||||
copy.nativeFunctions[name] = std::move (callback);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** Registers a NativeEventListener that receives events sent to the specified eventId.
|
||||
|
||||
To send a message to this listener from the frontend, call for example
|
||||
@code window.__JUCE__.backend.emitEvent(eventId, { x: 2, y: 6 }); @endcode
|
||||
*/
|
||||
[[nodiscard]] Options withEventListener (const Identifier& eventId, NativeEventListener listener) const
|
||||
{
|
||||
auto copy = *this;
|
||||
copy.eventListeners.emplace_back (eventId, std::move (listener));
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** Adds a Javascript code that will be evaluated before any other resource is loaded but
|
||||
after the JUCE backend definitions become available, hence the specified script can
|
||||
rely on the presence of `window.__JUCE__.backend`.
|
||||
|
||||
This script will be evaluated after all goToUrl() calls.
|
||||
*/
|
||||
[[nodiscard]] Options withUserScript (StringRef script) const
|
||||
{
|
||||
auto copy = *this;
|
||||
copy.userScripts.add (script);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** Ensures that there will be a Javascript Array under
|
||||
`window.__JUCE__.initialisationData.name` and that it will contain the value
|
||||
provided here.
|
||||
|
||||
The initialisation data is injected prior to loading any resource. Multiple values added
|
||||
for the same name will all be available in the Array.
|
||||
*/
|
||||
[[nodiscard]] Options withInitialisationData (StringRef name, const var& value) const
|
||||
{
|
||||
auto copy = *this;
|
||||
copy.initialisationData.push_back (std::make_pair (String { name }, value));
|
||||
return copy;
|
||||
}
|
||||
|
||||
#if JUCE_WEB_BROWSER_RESOURCE_PROVIDER_AVAILABLE || JUCE_DOXYGEN
|
||||
/** Sets a ResourceProvider object that can complete WebView resource requests and return
|
||||
data without having to issue a network operation.
|
||||
|
||||
Requests sent to WebBrowserComponent::getResourceProviderRoot() + "resource.path" will
|
||||
invoke the provider with the path "/resource.path".
|
||||
|
||||
If you call WebBrowserComponent::goToURL with the value returned by
|
||||
WebBrowserComponent::getResourceProviderRoot, your resource provider will receive a
|
||||
request for the resource "/" for which you will typically want to return the contents of
|
||||
your index.html.
|
||||
|
||||
You can also specify an optional allowedOriginIn parameter that will make your
|
||||
ResourceProvider available to scripts loaded from that origin. E.g. if you specify
|
||||
"http://localhost:3000", then a script loaded from such a local development server will
|
||||
be able to access resources such as getResourceProviderRoot() + "live_data.bin".
|
||||
|
||||
Allowing external origins is handy for development, but is a potential security risk in
|
||||
publicly released binaries.
|
||||
*/
|
||||
[[nodiscard]] Options withResourceProvider (ResourceProvider provider,
|
||||
std::optional<String> allowedOriginIn = std::nullopt) const
|
||||
{
|
||||
return withMember (withMember (*this, &Options::resourceProvider, std::move (provider)),
|
||||
&Options::allowedOrigin,
|
||||
std::move (allowedOriginIn));
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Adds an object that will be notified when the WebBrowserComponent is constructed and
|
||||
destructed.
|
||||
*/
|
||||
[[nodiscard]] Options withWebViewLifetimeListener (WebViewLifetimeListener* listener)
|
||||
{
|
||||
auto copy = *this;
|
||||
copy.lifetimeListeners.push_back (listener);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** Adds all options provided by the builder to the returned Options object.
|
||||
*/
|
||||
template <typename OptionsType>
|
||||
OptionsType withOptionsFrom (OptionsBuilder<OptionsType>& builder) const
|
||||
{
|
||||
return builder.buildOptions (static_cast<const OptionsType&> (*this));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
auto getBackend() const noexcept { return browserBackend; }
|
||||
auto keepsPageLoadedWhenBrowserIsHidden() const noexcept { return keepPageLoadedWhenBrowserIsHidden; }
|
||||
auto getUserAgent() const { return userAgent; }
|
||||
auto getWinWebView2BackendOptions() const { return winWebView2; }
|
||||
auto getAppleWkWebViewOptions() const { return appleWkWebView; }
|
||||
auto getNativeIntegrationsEnabled() const { return enableNativeIntegration; }
|
||||
const auto& getNativeFunctions() const { return nativeFunctions; }
|
||||
const auto& getEventListeners() const { return eventListeners; }
|
||||
const auto& getUserScripts() const { return userScripts; }
|
||||
const auto& getInitialisationData() const { return initialisationData; }
|
||||
auto getResourceProvider() const { return resourceProvider; }
|
||||
const auto& getAllowedOrigin() const { return allowedOrigin; }
|
||||
const auto& getLifetimeListeners() const { return lifetimeListeners; }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Backend browserBackend = Backend::defaultBackend;
|
||||
bool keepPageLoadedWhenBrowserIsHidden = false;
|
||||
bool enableNativeIntegration = false;
|
||||
String userAgent;
|
||||
WinWebView2 winWebView2;
|
||||
AppleWkWebView appleWkWebView;
|
||||
std::map<Identifier, NativeFunction> nativeFunctions;
|
||||
std::vector<std::pair<Identifier, NativeEventListener>> eventListeners;
|
||||
StringArray userScripts;
|
||||
std::vector<std::pair<String, var>> initialisationData;
|
||||
ResourceProvider resourceProvider;
|
||||
std::optional<String> allowedOrigin;
|
||||
std::vector<WebViewLifetimeListener*> lifetimeListeners;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a WebBrowserComponent with default options*/
|
||||
WebBrowserComponent() : WebBrowserComponent (Options {}) {}
|
||||
|
||||
/** Creates a WebBrowserComponent.
|
||||
|
||||
Once it's created and visible, send the browser to a URL using goToURL().
|
||||
*/
|
||||
explicit WebBrowserComponent (const Options& options);
|
||||
|
||||
/** Destructor. */
|
||||
~WebBrowserComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Check if the specified options are supported on this platform. */
|
||||
static bool areOptionsSupported (const Options& options);
|
||||
|
||||
//==============================================================================
|
||||
/** Sends the browser to a particular URL.
|
||||
|
||||
@param url the URL to go to.
|
||||
@param headers an optional set of parameters to put in the HTTP header. If
|
||||
you supply this, it should be a set of string in the form
|
||||
"HeaderKey: HeaderValue"
|
||||
@param postData an optional block of data that will be attached to the HTTP
|
||||
POST request
|
||||
*/
|
||||
void goToURL (const String& url,
|
||||
const StringArray* headers = nullptr,
|
||||
const MemoryBlock* postData = nullptr);
|
||||
|
||||
/** Stops the current page loading. */
|
||||
void stop();
|
||||
|
||||
/** Sends the browser back one page. */
|
||||
void goBack();
|
||||
|
||||
/** Sends the browser forward one page. */
|
||||
void goForward();
|
||||
|
||||
/** Refreshes the browser. */
|
||||
void refresh();
|
||||
|
||||
/** Clear cookies that the OS has stored for the WebComponents of this application */
|
||||
static void clearCookies();
|
||||
|
||||
/** Returns a platform specific string that represents the root address for resources served
|
||||
by the ResourceProvider.
|
||||
|
||||
If you pass this value to goToURL the ResourceProvider will receive a request with the "/"
|
||||
path parameter. In response to this request the provider may typically want to return the
|
||||
contents of the "index.html" file.
|
||||
|
||||
@see ResourceProvider, Options::withResourceProvider, goToURL
|
||||
*/
|
||||
static const String& getResourceProviderRoot();
|
||||
|
||||
//==============================================================================
|
||||
/** On MacOS, iOS and Linux getResult will return a nullptr if the evaluation failed. In this
|
||||
case getError will return a non-null Error result, which contains more information about
|
||||
why the evaluation failed. It could be e.g. a syntax error or referencing an undefined
|
||||
object.
|
||||
|
||||
On Windows and Android getResult will always return a valid pointer to a variant, and
|
||||
getError will always return a nullptr. In case there was an evaluation failure, getResult
|
||||
returns a void variant, which is indistinguishable from a successful evaluation that yielded
|
||||
a null result. Unfortunately these platforms don't offer a way to detect evaluation errors.
|
||||
*/
|
||||
class EvaluationResult
|
||||
{
|
||||
public:
|
||||
/** A simple error type class. */
|
||||
struct Error
|
||||
{
|
||||
/** Error type. */
|
||||
enum class Type
|
||||
{
|
||||
/** Error occurring for a reason unknown to us. */
|
||||
unknown,
|
||||
|
||||
/** Error occurring because of a Javascript exception being thrown. */
|
||||
javascriptException,
|
||||
|
||||
/** Error occurring because the returned result cannot be serialised into a native
|
||||
type e.g. Promise.
|
||||
*/
|
||||
unsupportedReturnType
|
||||
};
|
||||
|
||||
Type type;
|
||||
String message;
|
||||
};
|
||||
|
||||
EvaluationResult (const var& result) : value { result }
|
||||
{
|
||||
}
|
||||
|
||||
EvaluationResult (const Error& error) : value { error }
|
||||
{
|
||||
}
|
||||
|
||||
const var* getResult() const
|
||||
{
|
||||
return std::get_if<var> (&value);
|
||||
}
|
||||
|
||||
const Error* getError() const
|
||||
{
|
||||
return std::get_if<Error> (&value);
|
||||
}
|
||||
|
||||
private:
|
||||
std::variant<var, Error> value;
|
||||
};
|
||||
|
||||
/** Callback type that can be passed optionally to evaluateJavascript. */
|
||||
using EvaluationCallback = std::function<void (EvaluationResult)>;
|
||||
|
||||
/** Evaluates the specified script in the context of the current state of the
|
||||
WebBrowserComponent.
|
||||
|
||||
If the optional callback is provided it will be called with the result of the evaluation.
|
||||
The callback will be called on the message thread.
|
||||
*/
|
||||
void evaluateJavascript (const String& script, EvaluationCallback callback = nullptr);
|
||||
|
||||
/** Emits an object on the frontend under the specified eventId.
|
||||
|
||||
Ids beginning with `__juce` are reserved for the framework implementation.
|
||||
|
||||
Example for listening to such events on the frontend:
|
||||
@code
|
||||
// Subscribing
|
||||
const removalToken = window.__JUCE__.backend.addEventListener(eventId, (objectFromBackend) => {
|
||||
console.log(objectFromBackend.message);
|
||||
});
|
||||
|
||||
// Unsubscribing
|
||||
window.__JUCE__.backend.removeEventListener(removalToken);
|
||||
@endcode
|
||||
*/
|
||||
void emitEventIfBrowserIsVisible (const Identifier& eventId, const var& object);
|
||||
|
||||
//==============================================================================
|
||||
/** This callback is called when the browser is about to navigate
|
||||
to a new location.
|
||||
|
||||
You can override this method to perform some action when the user
|
||||
tries to go to a particular URL. To allow the operation to carry on,
|
||||
return true, or return false to stop the navigation happening.
|
||||
*/
|
||||
virtual bool pageAboutToLoad (const String& newURL);
|
||||
|
||||
/** This callback happens when the browser has finished loading a page. */
|
||||
virtual void pageFinishedLoading (const String& url);
|
||||
|
||||
/** This callback happens when a network error was encountered while
|
||||
trying to load a page.
|
||||
|
||||
You can override this method to show some other error page by calling
|
||||
goToURL. Return true to allow the browser to carry on to the internal
|
||||
browser error page.
|
||||
|
||||
The errorInfo contains some platform dependent string describing the
|
||||
error.
|
||||
|
||||
Calling goToURL() inside this callback can encounter further network
|
||||
errors, potentially causing an infinite loop.
|
||||
*/
|
||||
virtual bool pageLoadHadNetworkError (const String& errorInfo);
|
||||
|
||||
/** This callback occurs when a script or other activity in the browser asks for
|
||||
the window to be closed.
|
||||
*/
|
||||
virtual void windowCloseRequest();
|
||||
|
||||
/** This callback occurs when the browser attempts to load a URL in a new window.
|
||||
This won't actually load the window but gives you a chance to either launch a
|
||||
new window yourself or just load the URL into the current window with goToURL().
|
||||
*/
|
||||
virtual void newWindowAttemptingToLoad (const String& newURL);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
/** @internal */
|
||||
void visibilityChanged() override;
|
||||
/** @internal */
|
||||
void focusGainedWithDirection (FocusChangeType, FocusChangeDirection) override;
|
||||
|
||||
/** @internal */
|
||||
class Impl;
|
||||
|
||||
private:
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
std::unique_ptr<Impl> impl;
|
||||
bool blankPageShown = false, unloadPageWhenHidden;
|
||||
String lastURL;
|
||||
StringArray lastHeaders;
|
||||
MemoryBlock lastPostData;
|
||||
ListenerList<WebViewLifetimeListener> lifetimeListeners;
|
||||
|
||||
void reloadLastURL();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebBrowserComponent)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_WEB_BROWSER || DOXYGEN
|
||||
|
||||
/** This is a helper class for implementing AudioProcessorEditor::getControlParameterIndex with GUIs
|
||||
using a WebBrowserComponent.
|
||||
|
||||
Create an instance of this class and attach it to the WebBrowserComponent by using
|
||||
WebBrowserComponent::Options::withOptionsFrom.
|
||||
|
||||
In your frontend code you can use the ControlParameterIndexUpdater class, that emits
|
||||
controlParameterIndexChanged events based on the mouse movement, and control parameter index
|
||||
annotations attached to DOM elements.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API WebControlParameterIndexReceiver : public OptionsBuilder<WebBrowserComponent::Options>
|
||||
{
|
||||
public:
|
||||
/* Returns the control parameter index last reported by the WebBrowserComponent GUI to be
|
||||
active.
|
||||
*/
|
||||
int getControlParameterIndex() const { return controlParameterIndex; }
|
||||
|
||||
//==============================================================================
|
||||
WebBrowserComponent::Options buildOptions (const WebBrowserComponent::Options& initialOptions) override
|
||||
{
|
||||
return initialOptions.withEventListener ("__juce__controlParameterIndexChanged",
|
||||
[this] (auto newIndex)
|
||||
{
|
||||
controlParameterIndex = (int) newIndex;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
int controlParameterIndex = -1;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_WEB_BROWSER
|
||||
|
||||
WebSliderRelay::WebSliderRelay (StringRef nameIn)
|
||||
: name (nameIn)
|
||||
{
|
||||
}
|
||||
|
||||
void WebSliderRelay::setValue (float newValue)
|
||||
{
|
||||
using namespace detail;
|
||||
|
||||
DynamicObject::Ptr object { new DynamicObject };
|
||||
object->setProperty (WebSliderRelayEvents::Event::eventTypeKey,
|
||||
WebSliderRelayEvents::ValueChanged::eventId.toString());
|
||||
object->setProperty (WebSliderRelayEvents::ValueChanged::newValueKey, newValue);
|
||||
|
||||
value = newValue;
|
||||
emitEvent (object.get());
|
||||
}
|
||||
|
||||
float WebSliderRelay::getValue() const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
void WebSliderRelay::addListener (Listener* l)
|
||||
{
|
||||
listeners.add (l);
|
||||
}
|
||||
|
||||
void WebSliderRelay::removeListener (Listener* l)
|
||||
{
|
||||
listeners.remove (l);
|
||||
}
|
||||
|
||||
WebBrowserComponent::Options WebSliderRelay::buildOptions (const WebBrowserComponent::Options& initialOptions)
|
||||
{
|
||||
return initialOptions
|
||||
.withEventListener (eventId, [this] (auto object) { handleEvent (object); })
|
||||
.withInitialisationData ("__juce__sliders", name)
|
||||
.withWebViewLifetimeListener (this);
|
||||
}
|
||||
|
||||
void WebSliderRelay::emitEvent (const var& payload)
|
||||
{
|
||||
if (browser != nullptr)
|
||||
browser->emitEventIfBrowserIsVisible (eventId, payload);
|
||||
}
|
||||
|
||||
void WebSliderRelay::webViewConstructed (WebBrowserComponent* browserIn)
|
||||
{
|
||||
browser = browserIn;
|
||||
listeners.call (&Listener::initialUpdateRequested, this);
|
||||
}
|
||||
|
||||
void WebSliderRelay::webViewDestructed (WebBrowserComponent*)
|
||||
{
|
||||
browser = nullptr;
|
||||
}
|
||||
|
||||
void WebSliderRelay::handleEvent (const var& event)
|
||||
{
|
||||
using namespace detail;
|
||||
|
||||
if (const auto sliderEvent = WebSliderRelayEvents::Event::extract (event))
|
||||
{
|
||||
if (const auto valueChanged = WebSliderRelayEvents::ValueChanged::extract (*sliderEvent))
|
||||
{
|
||||
if (! approximatelyEqual (std::exchange (value, valueChanged->newValue), valueChanged->newValue))
|
||||
listeners.call ([this] (Listener& l) { l.sliderValueChanged (this); });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto dragStarted = WebSliderRelayEvents::SliderDragStarted::extract (*sliderEvent))
|
||||
{
|
||||
listeners.call ([this] (Listener& l) { l.sliderDragStarted (this); });
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto dragEnded = WebSliderRelayEvents::SliderDragEnded::extract (*sliderEvent))
|
||||
{
|
||||
listeners.call ([this] (Listener& l) { l.sliderDragEnded (this); });
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto initialUpdate =
|
||||
WebSliderRelayEvents::InitialUpdateRequested::extract (*sliderEvent))
|
||||
{
|
||||
listeners.call ([this] (Listener& l) { l.initialUpdateRequested (this); });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto s = JSON::toString (event);
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
WebToggleButtonRelay::WebToggleButtonRelay (StringRef nameIn)
|
||||
: name (nameIn)
|
||||
{
|
||||
}
|
||||
|
||||
void WebToggleButtonRelay::setToggleState (bool newState)
|
||||
{
|
||||
using namespace detail;
|
||||
|
||||
DynamicObject::Ptr object { new DynamicObject };
|
||||
object->setProperty (WebToggleButtonRelayEvents::Event::eventTypeKey,
|
||||
WebToggleButtonRelayEvents::ToggleStateChanged::eventId.toString());
|
||||
object->setProperty (WebToggleButtonRelayEvents::ToggleStateChanged::valueKey, newState);
|
||||
|
||||
emitEvent (object.get());
|
||||
}
|
||||
|
||||
void WebToggleButtonRelay::addListener (Listener* l)
|
||||
{
|
||||
listeners.add (l);
|
||||
}
|
||||
|
||||
void WebToggleButtonRelay::removeListener (Listener* l)
|
||||
{
|
||||
listeners.remove (l);
|
||||
}
|
||||
|
||||
WebBrowserComponent::Options WebToggleButtonRelay::buildOptions (const WebBrowserComponent::Options& initialOptions)
|
||||
{
|
||||
return initialOptions
|
||||
.withEventListener (eventId, [this] (auto object) { handleEvent (object); })
|
||||
.withInitialisationData ("__juce__toggles", name)
|
||||
.withWebViewLifetimeListener (this);
|
||||
}
|
||||
|
||||
void WebToggleButtonRelay::emitEvent (const var& payload)
|
||||
{
|
||||
if (browser != nullptr)
|
||||
browser->emitEventIfBrowserIsVisible (eventId, payload);
|
||||
}
|
||||
|
||||
void WebToggleButtonRelay::webViewConstructed (WebBrowserComponent* browserIn)
|
||||
{
|
||||
browser = browserIn;
|
||||
listeners.call (&Listener::initialUpdateRequested);
|
||||
}
|
||||
|
||||
void WebToggleButtonRelay::webViewDestructed (WebBrowserComponent*)
|
||||
{
|
||||
browser = nullptr;
|
||||
}
|
||||
|
||||
void WebToggleButtonRelay::handleEvent (const var& event)
|
||||
{
|
||||
using namespace detail;
|
||||
|
||||
if (const auto buttonEvent = WebToggleButtonRelayEvents::Event::extract (event))
|
||||
{
|
||||
if (const auto toggleStateChanged = WebToggleButtonRelayEvents::ToggleStateChanged::extract (*buttonEvent))
|
||||
{
|
||||
listeners.call ([&toggleStateChanged] (Listener& l)
|
||||
{ l.toggleStateChanged (toggleStateChanged->value); });
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto initialUpdate = WebToggleButtonRelayEvents::InitialUpdateRequested::extract (*buttonEvent))
|
||||
{
|
||||
listeners.call ([] (Listener& l) { l.initialUpdateRequested(); });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto s = JSON::toString (event);
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
WebComboBoxRelay::WebComboBoxRelay (StringRef nameIn)
|
||||
: name (nameIn)
|
||||
{
|
||||
}
|
||||
|
||||
void WebComboBoxRelay::setValue (float newValue)
|
||||
{
|
||||
using namespace detail;
|
||||
|
||||
DynamicObject::Ptr object { new DynamicObject };
|
||||
object->setProperty (WebComboBoxRelayEvents::Event::eventTypeKey,
|
||||
WebComboBoxRelayEvents::ValueChanged::eventId.toString());
|
||||
object->setProperty (WebComboBoxRelayEvents::ValueChanged::valueKey, newValue);
|
||||
|
||||
emitEvent (object.get());
|
||||
}
|
||||
|
||||
void WebComboBoxRelay::addListener (Listener* l)
|
||||
{
|
||||
listeners.add (l);
|
||||
}
|
||||
|
||||
void WebComboBoxRelay::removeListener (Listener* l)
|
||||
{
|
||||
listeners.remove (l);
|
||||
}
|
||||
|
||||
WebBrowserComponent::Options WebComboBoxRelay::buildOptions (const WebBrowserComponent::Options& initialOptions)
|
||||
{
|
||||
return initialOptions
|
||||
.withEventListener (eventId, [this] (auto object) { handleEvent (object); })
|
||||
.withInitialisationData ("__juce__comboBoxes", name)
|
||||
.withWebViewLifetimeListener (this);
|
||||
}
|
||||
|
||||
void WebComboBoxRelay::emitEvent (const var& payload)
|
||||
{
|
||||
if (browser != nullptr)
|
||||
browser->emitEventIfBrowserIsVisible (eventId, payload);
|
||||
}
|
||||
|
||||
void WebComboBoxRelay::webViewConstructed (WebBrowserComponent* browserIn)
|
||||
{
|
||||
browser = browserIn;
|
||||
listeners.call (&Listener::initialUpdateRequested);
|
||||
}
|
||||
|
||||
void WebComboBoxRelay::webViewDestructed (WebBrowserComponent*)
|
||||
{
|
||||
browser = nullptr;
|
||||
}
|
||||
|
||||
void WebComboBoxRelay::handleEvent (const var& event)
|
||||
{
|
||||
using namespace detail;
|
||||
|
||||
if (const auto buttonEvent = WebComboBoxRelayEvents::Event::extract (event))
|
||||
{
|
||||
if (const auto valueChanged = WebComboBoxRelayEvents::ValueChanged::extract (*buttonEvent))
|
||||
{
|
||||
listeners.call ([&valueChanged] (Listener& l)
|
||||
{ l.valueChanged (valueChanged->value); });
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto initialUpdate = WebComboBoxRelayEvents::InitialUpdateRequested::extract (*buttonEvent))
|
||||
{
|
||||
listeners.call ([] (Listener& l) { l.initialUpdateRequested(); });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto s = JSON::toString (event);
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_WEB_BROWSER || DOXYGEN
|
||||
|
||||
/** Helper class that relays audio parameter information to an object inside a WebBrowserComponent.
|
||||
|
||||
In order to create a relay you need to specify an identifier for the relayed state. This will
|
||||
result in a Javascript object becoming available inside the WebBrowserComponent under the
|
||||
provided identifier.
|
||||
|
||||
Pass the relay object to WebBrowserComponent::Options::withOptionsFrom() to associate it with
|
||||
a WebBrowserComponent instance.
|
||||
|
||||
You can then use a WebSliderParameterAttachment as you would a SliderAttachment, to attach the
|
||||
relay to a RangedAudioParameter. This will synchronise the state and events of the Javascript
|
||||
object with the audio parameter at all times.
|
||||
|
||||
@code
|
||||
// Add a relay to your AudioProcessorEditor members
|
||||
WebSliderRelay cutoffSliderRelay { "cutoffSlider" };
|
||||
WebBrowserComponent webComponent { WebBrowserComponent::Options{}::withOptionsFrom (cutoffSliderRelay) };
|
||||
@endcode
|
||||
|
||||
@code
|
||||
// In your Javascript GUI code you obtain an object from the framework
|
||||
import * as Juce from "juce-framework-frontend";
|
||||
const sliderState = Juce.getSliderState("cutoffSlider");
|
||||
@endcode
|
||||
|
||||
@see WebSliderParameterAttachment
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API WebSliderRelay : public OptionsBuilder<WebBrowserComponent::Options>,
|
||||
private WebViewLifetimeListener
|
||||
{
|
||||
public:
|
||||
/** Creating a relay will ensure that a Javascript object under the provided name will be
|
||||
available in the specified WebBrowserComponent's context. Use the frontend framework's
|
||||
getSliderState function with the same name to get a hold of this object.
|
||||
*/
|
||||
WebSliderRelay (StringRef nameIn);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
struct Listener : public SliderListener<WebSliderRelay>
|
||||
{
|
||||
virtual void initialUpdateRequested (WebSliderRelay*) = 0;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
void setValue (float newValue);
|
||||
|
||||
/** @internal */
|
||||
float getValue() const;
|
||||
|
||||
/** @internal */
|
||||
void addListener (Listener* l);
|
||||
|
||||
/** @internal */
|
||||
void removeListener (Listener* l);
|
||||
|
||||
/** @internal */
|
||||
WebBrowserComponent::Options buildOptions (const WebBrowserComponent::Options& initialOptions) override;
|
||||
|
||||
/** @internal */
|
||||
void emitEvent (const var& payload);
|
||||
|
||||
private:
|
||||
void handleEvent (const var& event);
|
||||
void webViewConstructed (WebBrowserComponent*) override;
|
||||
void webViewDestructed (WebBrowserComponent*) override;
|
||||
|
||||
WebBrowserComponent* browser = nullptr;
|
||||
String name;
|
||||
float value{};
|
||||
Identifier eventId { "__juce__slider" + name };
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (WebSliderRelay)
|
||||
JUCE_DECLARE_NON_MOVEABLE (WebSliderRelay)
|
||||
};
|
||||
|
||||
/** Helper class that relays audio parameter information to an object inside a WebBrowserComponent.
|
||||
|
||||
In order to create a relay you need to specify an identifier for the relayed state. This will
|
||||
result in a Javascript object becoming available inside the WebBrowserComponent under the
|
||||
provided identifier.
|
||||
|
||||
Pass the relay object to WebBrowserComponent::Options::withOptionsFrom() to associate it with
|
||||
a WebBrowserComponent instance.
|
||||
|
||||
You can then use a WebToggleButtonParameterAttachment as you would a ButtonParameterAttachment,
|
||||
to attach the relay to a RangedAudioParameter. This will synchronise the state and events of
|
||||
the Javascript object with the audio parameter at all times.
|
||||
|
||||
@code
|
||||
// Add a relay to your AudioProcessorEditor members
|
||||
WebToggleButtonRelay muteToggleRelay { "muteToggle" };
|
||||
WebBrowserComponent webComponent { WebBrowserComponent::Options{}::withOptionsFrom (muteToggleRelay) };
|
||||
@endcode
|
||||
|
||||
@code
|
||||
// In your Javascript GUI code you obtain an object from the framework
|
||||
import * as Juce from "juce-framework-frontend";
|
||||
const checkboxState = Juce.getToggleState("muteToggle");
|
||||
@endcode
|
||||
|
||||
@see WebToggleButtonParameterAttachment
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API WebToggleButtonRelay : public OptionsBuilder<WebBrowserComponent::Options>,
|
||||
private WebViewLifetimeListener
|
||||
{
|
||||
public:
|
||||
/** Creating a relay will ensure that a Javascript object under the provided name will be
|
||||
available in the specified WebBrowserComponent's context. Use the frontend framework's
|
||||
getToggleState function with the same name to get a hold of this object.
|
||||
*/
|
||||
WebToggleButtonRelay (StringRef nameIn);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
struct Listener
|
||||
{
|
||||
virtual ~Listener() = default;
|
||||
virtual void toggleStateChanged (bool) = 0;
|
||||
virtual void initialUpdateRequested() = 0;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
void setToggleState (bool newState);
|
||||
|
||||
/** @internal */
|
||||
void addListener (Listener* l);
|
||||
|
||||
/** @internal */
|
||||
void removeListener (Listener* l);
|
||||
|
||||
/** @internal */
|
||||
WebBrowserComponent::Options buildOptions (const WebBrowserComponent::Options& initialOptions) override;
|
||||
|
||||
/** @internal */
|
||||
void emitEvent (const var& payload);
|
||||
|
||||
private:
|
||||
void handleEvent (const var& event);
|
||||
void webViewConstructed (WebBrowserComponent*) override;
|
||||
void webViewDestructed (WebBrowserComponent*) override;
|
||||
|
||||
WebBrowserComponent* browser = nullptr;
|
||||
String name;
|
||||
Identifier eventId { "__juce__toggle" + name };
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (WebToggleButtonRelay)
|
||||
JUCE_DECLARE_NON_MOVEABLE (WebToggleButtonRelay)
|
||||
};
|
||||
|
||||
/** Helper class that relays audio parameter information to an object inside a WebBrowserComponent.
|
||||
|
||||
In order to create a relay you need to specify an identifier for the relayed state. This will
|
||||
result in a Javascript object becoming available inside the WebBrowserComponent under the
|
||||
provided identifier.
|
||||
|
||||
Pass the relay object to WebBrowserComponent::Options::withOptionsFrom() to associate it with
|
||||
a WebBrowserComponent instance.
|
||||
|
||||
You can then use a WebComboBoxParameterAttachment as you would a ComboBoxParameterAttachment,
|
||||
to attach the relay to a RangedAudioParameter. This will synchronise the state and events of
|
||||
the Javascript object with the audio parameter at all times.
|
||||
|
||||
@code
|
||||
// Add a relay to your AudioProcessorEditor members
|
||||
WebComboBoxRelay filterTypeComboRelay { "filterTypeCombo" };
|
||||
WebBrowserComponent webComponent { WebBrowserComponent::Options{}::withOptionsFrom (filterTypeComboRelay) };
|
||||
@endcode
|
||||
|
||||
@code
|
||||
// In your Javascript GUI code you obtain an object from the framework
|
||||
import * as Juce from "juce-framework-frontend";
|
||||
const comboBoxState = Juce.getComboBoxState("filterTypeCombo");
|
||||
@endcode
|
||||
|
||||
@see WebComboBoxParameterAttachment
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API WebComboBoxRelay : public OptionsBuilder<WebBrowserComponent::Options>,
|
||||
private WebViewLifetimeListener
|
||||
{
|
||||
public:
|
||||
/** Creating a relay will ensure that a Javascript object under the provided name will be
|
||||
available in the specified WebBrowserComponent's context. Use the frontend framework's
|
||||
getComboBoxState function with the same name to get a hold of this object.
|
||||
*/
|
||||
WebComboBoxRelay (StringRef nameIn);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
struct Listener
|
||||
{
|
||||
virtual ~Listener() = default;
|
||||
virtual void valueChanged (float) = 0;
|
||||
virtual void initialUpdateRequested() = 0;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
void setValue (float newValue);
|
||||
|
||||
/** @internal */
|
||||
void addListener (Listener* l);
|
||||
|
||||
/** @internal */
|
||||
void removeListener (Listener* l);
|
||||
|
||||
/** @internal */
|
||||
WebBrowserComponent::Options buildOptions (const WebBrowserComponent::Options& initialOptions) override;
|
||||
|
||||
/** @internal */
|
||||
void emitEvent (const var& payload);
|
||||
|
||||
private:
|
||||
void handleEvent (const var& event);
|
||||
void webViewConstructed (WebBrowserComponent*) override;
|
||||
void webViewDestructed (WebBrowserComponent*) override;
|
||||
|
||||
WebBrowserComponent* browser = nullptr;
|
||||
String name;
|
||||
Identifier eventId { "__juce__comboBox" + name };
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (WebComboBoxRelay)
|
||||
JUCE_DECLARE_NON_MOVEABLE (WebComboBoxRelay)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
+356
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.http.SslError;
|
||||
import android.os.Build;
|
||||
import android.os.Message;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.webkit.SslErrorHandler;
|
||||
import android.webkit.WebChromeClient;
|
||||
|
||||
import java.lang.annotation.Native;
|
||||
import java.util.ArrayList;
|
||||
|
||||
//==============================================================================
|
||||
public class JuceWebViewClasses
|
||||
{
|
||||
static public class NativeInterface
|
||||
{
|
||||
private long host;
|
||||
private final Object hostLock = new Object ();
|
||||
|
||||
//==============================================================================
|
||||
public NativeInterface (long hostToUse)
|
||||
{
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
public void hostDeleted ()
|
||||
{
|
||||
synchronized (hostLock)
|
||||
{
|
||||
host = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
public void onPageStarted (WebView view, String url)
|
||||
{
|
||||
if (host == 0)
|
||||
return;
|
||||
|
||||
handleResourceRequest (host, view, url);
|
||||
}
|
||||
|
||||
public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)
|
||||
{
|
||||
synchronized (hostLock)
|
||||
{
|
||||
if (host != 0)
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
return handleResourceRequest (host, view, request.getUrl().toString());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void onPageFinished (WebView view, String url)
|
||||
{
|
||||
if (host == 0)
|
||||
return;
|
||||
|
||||
webViewPageLoadFinished (host, view, url);
|
||||
}
|
||||
|
||||
public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error)
|
||||
{
|
||||
if (host == 0)
|
||||
return;
|
||||
|
||||
webViewReceivedSslError (host, view, handler, error);
|
||||
}
|
||||
|
||||
public void onReceivedHttpError (WebView view, WebResourceRequest request, WebResourceResponse errorResponse)
|
||||
{
|
||||
if (host == 0)
|
||||
return;
|
||||
|
||||
webViewReceivedHttpError (host, view, request, errorResponse);
|
||||
}
|
||||
|
||||
public void onCloseWindow (WebView window)
|
||||
{
|
||||
if (host == 0)
|
||||
return;
|
||||
|
||||
webViewCloseWindowRequest (host, window);
|
||||
}
|
||||
|
||||
public boolean onCreateWindow (WebView view, boolean isDialog,
|
||||
boolean isUserGesture, Message resultMsg)
|
||||
{
|
||||
if (host == 0)
|
||||
return false;
|
||||
|
||||
webViewCreateWindowRequest (host, view);
|
||||
return false;
|
||||
}
|
||||
|
||||
public String postMessageHandler (String message)
|
||||
{
|
||||
synchronized (hostLock)
|
||||
{
|
||||
if (host != 0)
|
||||
{
|
||||
String result = postMessage (host, message);
|
||||
|
||||
if (result == null)
|
||||
return "";
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public void handleJavascriptEvaluationResult (long evalId, String result)
|
||||
{
|
||||
if (host == 0)
|
||||
return;
|
||||
|
||||
evaluationResultHandler (host, evalId, result);
|
||||
}
|
||||
|
||||
public boolean shouldOverrideUrlLoading (String url)
|
||||
{
|
||||
if (host == 0)
|
||||
return false;
|
||||
|
||||
return ! pageAboutToLoad (host, url);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
private native WebResourceResponse handleResourceRequest (long host, WebView view, String url);
|
||||
private native void webViewPageLoadFinished (long host, WebView view, String url);
|
||||
private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error);
|
||||
private native void webViewReceivedHttpError (long host, WebView view, WebResourceRequest request, WebResourceResponse errorResponse);
|
||||
|
||||
private native void webViewCloseWindowRequest (long host, WebView view);
|
||||
private native void webViewCreateWindowRequest (long host, WebView view);
|
||||
|
||||
private native void evaluationResultHandler (long host, long evalId, String result);
|
||||
private native String postMessage (long host, String message);
|
||||
|
||||
private native boolean pageAboutToLoad (long host, String url);
|
||||
}
|
||||
|
||||
static public class Client extends WebViewClient
|
||||
{
|
||||
private NativeInterface nativeInterface;
|
||||
|
||||
//==============================================================================
|
||||
public Client (NativeInterface nativeInterfaceIn)
|
||||
{
|
||||
nativeInterface = nativeInterfaceIn;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
@Override
|
||||
public void onPageFinished (WebView view, String url)
|
||||
{
|
||||
nativeInterface.onPageFinished (view, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error)
|
||||
{
|
||||
nativeInterface.onReceivedSslError (view, handler, error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedHttpError (WebView view, WebResourceRequest request, WebResourceResponse errorResponse)
|
||||
{
|
||||
nativeInterface.onReceivedHttpError (view, request, errorResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted (WebView view, String url, Bitmap favicon)
|
||||
{
|
||||
nativeInterface.onPageStarted (view, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)
|
||||
{
|
||||
return nativeInterface.shouldInterceptRequest (view, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading (WebView view, WebResourceRequest request)
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
return nativeInterface.shouldOverrideUrlLoading (request.getUrl().toString());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading (WebView view, String url)
|
||||
{
|
||||
return nativeInterface.shouldOverrideUrlLoading (url);
|
||||
}
|
||||
}
|
||||
|
||||
static public class ChromeClient extends WebChromeClient
|
||||
{
|
||||
private NativeInterface nativeInterface;
|
||||
|
||||
//==============================================================================
|
||||
public ChromeClient (NativeInterface nativeInterfaceIn)
|
||||
{
|
||||
nativeInterface = nativeInterfaceIn;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
@Override
|
||||
public void onCloseWindow (WebView window)
|
||||
{
|
||||
nativeInterface.onCloseWindow (window);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateWindow (WebView view, boolean isDialog,
|
||||
boolean isUserGesture, Message resultMsg)
|
||||
{
|
||||
return nativeInterface.onCreateWindow (view, isDialog, isUserGesture, resultMsg);
|
||||
}
|
||||
}
|
||||
|
||||
static public class WebAppInterface
|
||||
{
|
||||
private NativeInterface nativeInterface;
|
||||
String userScripts;
|
||||
|
||||
//==============================================================================
|
||||
public WebAppInterface (NativeInterface nativeInterfaceIn, String userScriptsIn)
|
||||
{
|
||||
nativeInterface = nativeInterfaceIn;
|
||||
userScripts = userScriptsIn;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
@JavascriptInterface
|
||||
public String postMessage (String message)
|
||||
{
|
||||
return nativeInterface.postMessageHandler (message);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String getAndroidUserScripts()
|
||||
{
|
||||
return userScripts;
|
||||
}
|
||||
}
|
||||
|
||||
static public class JuceWebView extends WebView
|
||||
{
|
||||
private NativeInterface nativeInterface;
|
||||
private long evaluationId = 0;
|
||||
|
||||
public JuceWebView (Context context, long host, String userAgent, String initScripts)
|
||||
{
|
||||
super (context);
|
||||
|
||||
nativeInterface = new NativeInterface (host);
|
||||
|
||||
WebSettings settings = getSettings();
|
||||
settings.setJavaScriptEnabled (true);
|
||||
settings.setBuiltInZoomControls (true);
|
||||
settings.setDisplayZoomControls (false);
|
||||
settings.setSupportMultipleWindows (true);
|
||||
settings.setUserAgentString (userAgent);
|
||||
|
||||
setWebChromeClient (new ChromeClient (nativeInterface));
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
|
||||
{
|
||||
setWebContentsDebuggingEnabled (true);
|
||||
}
|
||||
|
||||
setWebViewClient (new Client (nativeInterface));
|
||||
addJavascriptInterface (new WebAppInterface (nativeInterface, initScripts), "__JUCE__");
|
||||
}
|
||||
|
||||
public void disconnectNative()
|
||||
{
|
||||
nativeInterface.hostDeleted();
|
||||
}
|
||||
|
||||
public long evaluateJavascript (String script)
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
|
||||
{
|
||||
final long currentEvaluationId = evaluationId++;
|
||||
final NativeInterface accessibleNativeInterface = nativeInterface;
|
||||
|
||||
super.evaluateJavascript (script, new ValueCallback<String>()
|
||||
{
|
||||
@Override
|
||||
public void onReceiveValue(String result)
|
||||
{
|
||||
accessibleNativeInterface.handleJavascriptEvaluationResult (currentEvaluationId,
|
||||
result);
|
||||
}
|
||||
});
|
||||
|
||||
return currentEvaluationId;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import com.google.firebase.iid.*;
|
||||
|
||||
public final class JuceFirebaseInstanceIdService extends FirebaseInstanceIdService
|
||||
{
|
||||
private native void firebaseInstanceIdTokenRefreshed (String token);
|
||||
|
||||
@Override
|
||||
public void onTokenRefresh()
|
||||
{
|
||||
String token = FirebaseInstanceId.getInstance().getToken();
|
||||
|
||||
firebaseInstanceIdTokenRefreshed (token);
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import com.google.firebase.messaging.*;
|
||||
|
||||
public final class JuceFirebaseMessagingService extends FirebaseMessagingService
|
||||
{
|
||||
private native void firebaseRemoteMessageReceived (RemoteMessage message);
|
||||
private native void firebaseRemoteMessagesDeleted();
|
||||
private native void firebaseRemoteMessageSent (String messageId);
|
||||
private native void firebaseRemoteMessageSendError (String messageId, String error);
|
||||
|
||||
@Override
|
||||
public void onMessageReceived (RemoteMessage message)
|
||||
{
|
||||
firebaseRemoteMessageReceived (message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeletedMessages()
|
||||
{
|
||||
firebaseRemoteMessagesDeleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageSent (String messageId)
|
||||
{
|
||||
firebaseRemoteMessageSent (messageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendError (String messageId, Exception e)
|
||||
{
|
||||
firebaseRemoteMessageSendError (messageId, e.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
if (
|
||||
typeof window.__JUCE__ !== "undefined" &&
|
||||
typeof window.__JUCE__.getAndroidUserScripts !== "undefined" &&
|
||||
typeof window.inAndroidUserScriptEval === "undefined"
|
||||
) {
|
||||
window.inAndroidUserScriptEval = true;
|
||||
eval(window.__JUCE__.getAndroidUserScripts());
|
||||
delete window.inAndroidUserScriptEval;
|
||||
}
|
||||
|
||||
{
|
||||
if (typeof window.__JUCE__ === "undefined") {
|
||||
console.warn(
|
||||
"The 'window.__JUCE__' object is undefined." +
|
||||
" Native integration features will not work." +
|
||||
" Defining a placeholder 'window.__JUCE__' object."
|
||||
);
|
||||
|
||||
window.__JUCE__ = {
|
||||
postMessage: function () {},
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof window.__JUCE__.initialisationData === "undefined") {
|
||||
window.__JUCE__.initialisationData = {
|
||||
__juce__platform: [],
|
||||
__juce__functions: [],
|
||||
__juce__registeredGlobalEventIds: [],
|
||||
__juce__sliders: [],
|
||||
__juce__toggles: [],
|
||||
__juce__comboBoxes: [],
|
||||
};
|
||||
}
|
||||
|
||||
class ListenerList {
|
||||
constructor() {
|
||||
this.listeners = new Map();
|
||||
this.listenerId = 0;
|
||||
}
|
||||
|
||||
addListener(fn) {
|
||||
const newListenerId = this.listenerId++;
|
||||
this.listeners.set(newListenerId, fn);
|
||||
return newListenerId;
|
||||
}
|
||||
|
||||
removeListener(id) {
|
||||
if (this.listeners.has(id)) {
|
||||
this.listeners.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
callListeners(payload) {
|
||||
for (const [, value] of this.listeners) {
|
||||
value(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EventListenerList {
|
||||
constructor() {
|
||||
this.eventListeners = new Map();
|
||||
}
|
||||
|
||||
addEventListener(eventId, fn) {
|
||||
if (!this.eventListeners.has(eventId))
|
||||
this.eventListeners.set(eventId, new ListenerList());
|
||||
|
||||
const id = this.eventListeners.get(eventId).addListener(fn);
|
||||
|
||||
return [eventId, id];
|
||||
}
|
||||
|
||||
removeEventListener([eventId, id]) {
|
||||
if (this.eventListeners.has(eventId)) {
|
||||
this.eventListeners.get(eventId).removeListener(id);
|
||||
}
|
||||
}
|
||||
|
||||
emitEvent(eventId, object) {
|
||||
if (this.eventListeners.has(eventId))
|
||||
this.eventListeners.get(eventId).callListeners(object);
|
||||
}
|
||||
}
|
||||
|
||||
class Backend {
|
||||
constructor() {
|
||||
this.listeners = new EventListenerList();
|
||||
}
|
||||
|
||||
addEventListener(eventId, fn) {
|
||||
return this.listeners.addEventListener(eventId, fn);
|
||||
}
|
||||
|
||||
removeEventListener([eventId, id]) {
|
||||
this.listeners.removeEventListener([eventId, id]);
|
||||
}
|
||||
|
||||
emitEvent(eventId, object) {
|
||||
window.__JUCE__.postMessage(
|
||||
JSON.stringify({ eventId: eventId, payload: object })
|
||||
);
|
||||
}
|
||||
|
||||
emitByBackend(eventId, object) {
|
||||
this.listeners.emitEvent(eventId, JSON.parse(object));
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window.__JUCE__.backend === "undefined")
|
||||
window.__JUCE__.backend = new Backend();
|
||||
}
|
||||
@@ -0,0 +1,577 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
import "./check_native_interop.js";
|
||||
|
||||
class PromiseHandler {
|
||||
constructor() {
|
||||
this.lastPromiseId = 0;
|
||||
this.promises = new Map();
|
||||
|
||||
window.__JUCE__.backend.addEventListener(
|
||||
"__juce__complete",
|
||||
({ promiseId, result }) => {
|
||||
if (this.promises.has(promiseId)) {
|
||||
this.promises.get(promiseId).resolve(result);
|
||||
this.promises.delete(promiseId);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
createPromise() {
|
||||
const promiseId = this.lastPromiseId++;
|
||||
const result = new Promise((resolve, reject) => {
|
||||
this.promises.set(promiseId, { resolve: resolve, reject: reject });
|
||||
});
|
||||
return [promiseId, result];
|
||||
}
|
||||
}
|
||||
|
||||
const promiseHandler = new PromiseHandler();
|
||||
|
||||
/**
|
||||
* Returns a function object that calls a function registered on the JUCE backend and forwards all
|
||||
* parameters to it.
|
||||
*
|
||||
* The provided name should be the same as the name argument passed to
|
||||
* WebBrowserComponent::Options.withNativeFunction() on the backend.
|
||||
*
|
||||
* @param {String} name
|
||||
*/
|
||||
function getNativeFunction(name) {
|
||||
if (!window.__JUCE__.initialisationData.__juce__functions.includes(name))
|
||||
console.warn(
|
||||
`Creating native function binding for '${name}', which is unknown to the backend`
|
||||
);
|
||||
|
||||
const f = function () {
|
||||
const [promiseId, result] = promiseHandler.createPromise();
|
||||
|
||||
window.__JUCE__.backend.emitEvent("__juce__invoke", {
|
||||
name: name,
|
||||
params: Array.prototype.slice.call(arguments),
|
||||
resultId: promiseId,
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
||||
class ListenerList {
|
||||
constructor() {
|
||||
this.listeners = new Map();
|
||||
this.listenerId = 0;
|
||||
}
|
||||
|
||||
addListener(fn) {
|
||||
const newListenerId = this.listenerId++;
|
||||
this.listeners.set(newListenerId, fn);
|
||||
return newListenerId;
|
||||
}
|
||||
|
||||
removeListener(id) {
|
||||
if (this.listeners.has(id)) {
|
||||
this.listeners.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
callListeners(payload) {
|
||||
for (const [, value] of this.listeners) {
|
||||
value(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const BasicControl_valueChangedEventId = "valueChanged";
|
||||
const BasicControl_propertiesChangedId = "propertiesChanged";
|
||||
const SliderControl_sliderDragStartedEventId = "sliderDragStarted";
|
||||
const SliderControl_sliderDragEndedEventId = "sliderDragEnded";
|
||||
|
||||
/**
|
||||
* SliderState encapsulates data and callbacks that are synchronised with a WebSliderRelay object
|
||||
* on the backend.
|
||||
*
|
||||
* Use getSliderState() to create a SliderState object. This object will be synchronised with the
|
||||
* WebSliderRelay backend object that was created using the same unique name.
|
||||
*
|
||||
* @param {String} name
|
||||
*/
|
||||
class SliderState {
|
||||
constructor(name) {
|
||||
if (!window.__JUCE__.initialisationData.__juce__sliders.includes(name))
|
||||
console.warn(
|
||||
"Creating SliderState for '" +
|
||||
name +
|
||||
"', which is unknown to the backend"
|
||||
);
|
||||
|
||||
this.name = name;
|
||||
this.identifier = "__juce__slider" + this.name;
|
||||
this.scaledValue = 0;
|
||||
this.properties = {
|
||||
start: 0,
|
||||
end: 1,
|
||||
skew: 1,
|
||||
name: "",
|
||||
label: "",
|
||||
numSteps: 100,
|
||||
interval: 0,
|
||||
parameterIndex: -1,
|
||||
};
|
||||
this.valueChangedEvent = new ListenerList();
|
||||
this.propertiesChangedEvent = new ListenerList();
|
||||
|
||||
window.__JUCE__.backend.addEventListener(this.identifier, (event) =>
|
||||
this.handleEvent(event)
|
||||
);
|
||||
|
||||
window.__JUCE__.backend.emitEvent(this.identifier, {
|
||||
eventType: "requestInitialUpdate",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the normalised value of the corresponding backend parameter. This value is always in the
|
||||
* [0, 1] range (inclusive).
|
||||
*
|
||||
* The meaning of this range is the same as in the case of
|
||||
* AudioProcessorParameter::getValue() (C++).
|
||||
*
|
||||
* @param {String} name
|
||||
*/
|
||||
setNormalisedValue(newValue) {
|
||||
this.scaledValue = this.snapToLegalValue(
|
||||
this.normalisedToScaledValue(newValue)
|
||||
);
|
||||
|
||||
window.__JUCE__.backend.emitEvent(this.identifier, {
|
||||
eventType: BasicControl_valueChangedEventId,
|
||||
value: this.scaledValue,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function should be called first thing when the user starts interacting with the slider.
|
||||
*/
|
||||
sliderDragStarted() {
|
||||
window.__JUCE__.backend.emitEvent(this.identifier, {
|
||||
eventType: SliderControl_sliderDragStartedEventId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function should be called when the user finished the interaction with the slider.
|
||||
*/
|
||||
sliderDragEnded() {
|
||||
window.__JUCE__.backend.emitEvent(this.identifier, {
|
||||
eventType: SliderControl_sliderDragEndedEventId,
|
||||
});
|
||||
}
|
||||
|
||||
/** Internal. */
|
||||
handleEvent(event) {
|
||||
if (event.eventType == BasicControl_valueChangedEventId) {
|
||||
this.scaledValue = event.value;
|
||||
this.valueChangedEvent.callListeners();
|
||||
}
|
||||
if (event.eventType == BasicControl_propertiesChangedId) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let { eventType: _, ...rest } = event;
|
||||
this.properties = rest;
|
||||
this.propertiesChangedEvent.callListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scaled value of the parameter. This corresponds to the return value of
|
||||
* NormalisableRange::convertFrom0to1() (C++). This value will differ from a linear
|
||||
* [0, 1] range if a non-default NormalisableRange was set for the parameter.
|
||||
*/
|
||||
getScaledValue() {
|
||||
return this.scaledValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the normalised value of the corresponding backend parameter. This value is always in the
|
||||
* [0, 1] range (inclusive).
|
||||
*
|
||||
* The meaning of this range is the same as in the case of
|
||||
* AudioProcessorParameter::getValue() (C++).
|
||||
*
|
||||
* @param {String} name
|
||||
*/
|
||||
getNormalisedValue() {
|
||||
return Math.pow(
|
||||
(this.scaledValue - this.properties.start) /
|
||||
(this.properties.end - this.properties.start),
|
||||
this.properties.skew
|
||||
);
|
||||
}
|
||||
|
||||
/** Internal. */
|
||||
normalisedToScaledValue(normalisedValue) {
|
||||
return (
|
||||
Math.pow(normalisedValue, 1 / this.properties.skew) *
|
||||
(this.properties.end - this.properties.start) +
|
||||
this.properties.start
|
||||
);
|
||||
}
|
||||
|
||||
/** Internal. */
|
||||
snapToLegalValue(value) {
|
||||
const interval = this.properties.interval;
|
||||
|
||||
if (interval == 0) return value;
|
||||
|
||||
const start = this.properties.start;
|
||||
const clamp = (val, min = 0, max = 1) => Math.max(min, Math.min(max, val));
|
||||
|
||||
return clamp(
|
||||
start + interval * Math.floor((value - start) / interval + 0.5),
|
||||
this.properties.start,
|
||||
this.properties.end
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const sliderStates = new Map();
|
||||
|
||||
for (const sliderName of window.__JUCE__.initialisationData.__juce__sliders)
|
||||
sliderStates.set(sliderName, new SliderState(sliderName));
|
||||
|
||||
/**
|
||||
* Returns a SliderState object that is connected to the backend WebSliderRelay object that was
|
||||
* created with the same name argument.
|
||||
*
|
||||
* To register a WebSliderRelay object create one with the right name and add it to the
|
||||
* WebBrowserComponent::Options struct using withOptionsFrom.
|
||||
*
|
||||
* @param {String} name
|
||||
*/
|
||||
function getSliderState(name) {
|
||||
if (!sliderStates.has(name)) sliderStates.set(name, new SliderState(name));
|
||||
|
||||
return sliderStates.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* ToggleState encapsulates data and callbacks that are synchronised with a WebToggleRelay object
|
||||
* on the backend.
|
||||
*
|
||||
* Use getToggleState() to create a ToggleState object. This object will be synchronised with the
|
||||
* WebToggleRelay backend object that was created using the same unique name.
|
||||
*
|
||||
* @param {String} name
|
||||
*/
|
||||
class ToggleState {
|
||||
constructor(name) {
|
||||
if (!window.__JUCE__.initialisationData.__juce__toggles.includes(name))
|
||||
console.warn(
|
||||
"Creating ToggleState for '" +
|
||||
name +
|
||||
"', which is unknown to the backend"
|
||||
);
|
||||
|
||||
this.name = name;
|
||||
this.identifier = "__juce__toggle" + this.name;
|
||||
this.value = false;
|
||||
this.properties = {
|
||||
name: "",
|
||||
parameterIndex: -1,
|
||||
};
|
||||
this.valueChangedEvent = new ListenerList();
|
||||
this.propertiesChangedEvent = new ListenerList();
|
||||
|
||||
window.__JUCE__.backend.addEventListener(this.identifier, (event) =>
|
||||
this.handleEvent(event)
|
||||
);
|
||||
|
||||
window.__JUCE__.backend.emitEvent(this.identifier, {
|
||||
eventType: "requestInitialUpdate",
|
||||
});
|
||||
}
|
||||
|
||||
/** Returns the value corresponding to the associated WebToggleRelay's (C++) state. */
|
||||
getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/** Informs the backend to change the associated WebToggleRelay's (C++) state. */
|
||||
setValue(newValue) {
|
||||
this.value = newValue;
|
||||
|
||||
window.__JUCE__.backend.emitEvent(this.identifier, {
|
||||
eventType: BasicControl_valueChangedEventId,
|
||||
value: this.value,
|
||||
});
|
||||
}
|
||||
|
||||
/** Internal. */
|
||||
handleEvent(event) {
|
||||
if (event.eventType == BasicControl_valueChangedEventId) {
|
||||
this.value = event.value;
|
||||
this.valueChangedEvent.callListeners();
|
||||
}
|
||||
if (event.eventType == BasicControl_propertiesChangedId) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let { eventType: _, ...rest } = event;
|
||||
this.properties = rest;
|
||||
this.propertiesChangedEvent.callListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const toggleStates = new Map();
|
||||
|
||||
for (const name of window.__JUCE__.initialisationData.__juce__toggles)
|
||||
toggleStates.set(name, new ToggleState(name));
|
||||
|
||||
/**
|
||||
* Returns a ToggleState object that is connected to the backend WebToggleButtonRelay object that was
|
||||
* created with the same name argument.
|
||||
*
|
||||
* To register a WebToggleButtonRelay object create one with the right name and add it to the
|
||||
* WebBrowserComponent::Options struct using withOptionsFrom.
|
||||
*
|
||||
* @param {String} name
|
||||
*/
|
||||
function getToggleState(name) {
|
||||
if (!toggleStates.has(name)) toggleStates.set(name, new ToggleState(name));
|
||||
|
||||
return toggleStates.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* ComboBoxState encapsulates data and callbacks that are synchronised with a WebComboBoxRelay object
|
||||
* on the backend.
|
||||
*
|
||||
* Use getComboBoxState() to create a ComboBoxState object. This object will be synchronised with the
|
||||
* WebComboBoxRelay backend object that was created using the same unique name.
|
||||
*
|
||||
* @param {String} name
|
||||
*/
|
||||
class ComboBoxState {
|
||||
constructor(name) {
|
||||
if (!window.__JUCE__.initialisationData.__juce__comboBoxes.includes(name))
|
||||
console.warn(
|
||||
"Creating ComboBoxState for '" +
|
||||
name +
|
||||
"', which is unknown to the backend"
|
||||
);
|
||||
|
||||
this.name = name;
|
||||
this.identifier = "__juce__comboBox" + this.name;
|
||||
this.value = 0.0;
|
||||
this.properties = {
|
||||
name: "",
|
||||
parameterIndex: -1,
|
||||
choices: [],
|
||||
};
|
||||
this.valueChangedEvent = new ListenerList();
|
||||
this.propertiesChangedEvent = new ListenerList();
|
||||
|
||||
window.__JUCE__.backend.addEventListener(this.identifier, (event) =>
|
||||
this.handleEvent(event)
|
||||
);
|
||||
|
||||
window.__JUCE__.backend.emitEvent(this.identifier, {
|
||||
eventType: "requestInitialUpdate",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value corresponding to the associated WebComboBoxRelay's (C++) state.
|
||||
*
|
||||
* This is an index identifying which element of the properties.choices array is currently
|
||||
* selected.
|
||||
*/
|
||||
getChoiceIndex() {
|
||||
return Math.round(this.value * (this.properties.choices.length - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs the backend to change the associated WebComboBoxRelay's (C++) state.
|
||||
*
|
||||
* This should be called with the index identifying the selected element from the
|
||||
* properties.choices array.
|
||||
*/
|
||||
setChoiceIndex(index) {
|
||||
const numItems = this.properties.choices.length;
|
||||
this.value = numItems > 1 ? index / (numItems - 1) : 0.0;
|
||||
|
||||
window.__JUCE__.backend.emitEvent(this.identifier, {
|
||||
eventType: BasicControl_valueChangedEventId,
|
||||
value: this.value,
|
||||
});
|
||||
}
|
||||
|
||||
/** Internal. */
|
||||
handleEvent(event) {
|
||||
if (event.eventType == BasicControl_valueChangedEventId) {
|
||||
this.value = event.value;
|
||||
this.valueChangedEvent.callListeners();
|
||||
}
|
||||
if (event.eventType == BasicControl_propertiesChangedId) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let { eventType: _, ...rest } = event;
|
||||
this.properties = rest;
|
||||
this.propertiesChangedEvent.callListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const comboBoxStates = new Map();
|
||||
|
||||
for (const name of window.__JUCE__.initialisationData.__juce__comboBoxes)
|
||||
comboBoxStates.set(name, new ComboBoxState(name));
|
||||
|
||||
/**
|
||||
* Returns a ComboBoxState object that is connected to the backend WebComboBoxRelay object that was
|
||||
* created with the same name argument.
|
||||
*
|
||||
* To register a WebComboBoxRelay object create one with the right name and add it to the
|
||||
* WebBrowserComponent::Options struct using withOptionsFrom.
|
||||
*
|
||||
* @param {String} name
|
||||
*/
|
||||
function getComboBoxState(name) {
|
||||
if (!comboBoxStates.has(name))
|
||||
comboBoxStates.set(name, new ComboBoxState(name));
|
||||
|
||||
return comboBoxStates.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a platform-specific prefix to the path to ensure that a request sent to this address will
|
||||
* be received by the backend's ResourceProvider.
|
||||
* @param {String} path
|
||||
*/
|
||||
function getBackendResourceAddress(path) {
|
||||
const platform =
|
||||
window.__JUCE__.initialisationData.__juce__platform.length > 0
|
||||
? window.__JUCE__.initialisationData.__juce__platform[0]
|
||||
: "";
|
||||
|
||||
if (platform == "windows" || platform == "android")
|
||||
return "https://juce.backend/" + path;
|
||||
|
||||
if (platform == "macos" || platform == "ios" || platform == "linux")
|
||||
return "juce://juce.backend/" + path;
|
||||
|
||||
console.warn(
|
||||
"getBackendResourceAddress() called, but no JUCE native backend is detected."
|
||||
);
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* This helper class is intended to aid the implementation of
|
||||
* AudioProcessorEditor::getControlParameterIndex() for editors using a WebView interface.
|
||||
*
|
||||
* Create an instance of this class and call its handleMouseMove() method in each mousemove event.
|
||||
*
|
||||
* This class can be used to continuously report the controlParameterIndexAnnotation attribute's
|
||||
* value related to the DOM element that is currently under the mouse pointer.
|
||||
*
|
||||
* This value is defined at all times as follows
|
||||
* * the annotation attribute's value for the DOM element directly under the mouse, if it has it,
|
||||
* * the annotation attribute's value for the first parent element, that has it,
|
||||
* * -1 otherwise.
|
||||
*
|
||||
* Whenever there is a change in this value, an event is emitted to the frontend with the new value.
|
||||
* You can use a ControlParameterIndexReceiver object on the backend to listen to these events.
|
||||
*
|
||||
* @param {String} controlParameterIndexAnnotation
|
||||
*/
|
||||
class ControlParameterIndexUpdater {
|
||||
constructor(controlParameterIndexAnnotation) {
|
||||
this.controlParameterIndexAnnotation = controlParameterIndexAnnotation;
|
||||
this.lastElement = null;
|
||||
this.lastControlParameterIndex = null;
|
||||
}
|
||||
|
||||
handleMouseMove(event) {
|
||||
const currentElement = document.elementFromPoint(
|
||||
event.clientX,
|
||||
event.clientY
|
||||
);
|
||||
|
||||
if (currentElement === this.lastElement) return;
|
||||
this.lastElement = currentElement;
|
||||
|
||||
let controlParameterIndex = -1;
|
||||
|
||||
if (currentElement !== null)
|
||||
controlParameterIndex = this.#getControlParameterIndex(currentElement);
|
||||
|
||||
if (controlParameterIndex === this.lastControlParameterIndex) return;
|
||||
this.lastControlParameterIndex = controlParameterIndex;
|
||||
|
||||
window.__JUCE__.backend.emitEvent(
|
||||
"__juce__controlParameterIndexChanged",
|
||||
controlParameterIndex
|
||||
);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#getControlParameterIndex(element) {
|
||||
const isValidNonRootElement = (e) => {
|
||||
return e !== null && e !== document.documentElement;
|
||||
};
|
||||
|
||||
while (isValidNonRootElement(element)) {
|
||||
if (element.hasAttribute(this.controlParameterIndexAnnotation)) {
|
||||
return element.getAttribute(this.controlParameterIndexAnnotation);
|
||||
}
|
||||
|
||||
element = element.parentElement;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
getNativeFunction,
|
||||
getSliderState,
|
||||
getToggleState,
|
||||
getComboBoxState,
|
||||
getBackendResourceAddress,
|
||||
ControlParameterIndexUpdater,
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "juce-framework-frontend",
|
||||
"version": "7.0.7"
|
||||
}
|
||||
@@ -0,0 +1,553 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
extern int64 getMouseEventTime();
|
||||
|
||||
JUCE_DECLARE_UUID_GETTER (IOleObject, "00000112-0000-0000-C000-000000000046")
|
||||
JUCE_DECLARE_UUID_GETTER (IOleWindow, "00000114-0000-0000-C000-000000000046")
|
||||
JUCE_DECLARE_UUID_GETTER (IOleInPlaceSite, "00000119-0000-0000-C000-000000000046")
|
||||
|
||||
namespace ActiveXHelpers
|
||||
{
|
||||
//==============================================================================
|
||||
struct JuceIStorage final : public ComBaseClassHelper<IStorage>
|
||||
{
|
||||
JuceIStorage() = default;
|
||||
|
||||
JUCE_COMRESULT CreateStream (const WCHAR*, DWORD, DWORD, DWORD, IStream**) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT OpenStream (const WCHAR*, void*, DWORD, DWORD, IStream**) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT CreateStorage (const WCHAR*, DWORD, DWORD, DWORD, IStorage**) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT OpenStorage (const WCHAR*, IStorage*, DWORD, SNB, DWORD, IStorage**) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT CopyTo (DWORD, IID const*, SNB, IStorage*) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT MoveElementTo (const OLECHAR*,IStorage*, const OLECHAR*, DWORD) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT Commit (DWORD) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT Revert() override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT EnumElements (DWORD, void*, DWORD, IEnumSTATSTG**) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT DestroyElement (const OLECHAR*) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT RenameElement (const WCHAR*, const WCHAR*) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT SetElementTimes (const WCHAR*, FILETIME const*, FILETIME const*, FILETIME const*) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT SetClass (REFCLSID) override { return S_OK; }
|
||||
JUCE_COMRESULT SetStateBits (DWORD, DWORD) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT Stat (STATSTG*, DWORD) override { return E_NOTIMPL; }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct JuceOleInPlaceFrame final : public ComBaseClassHelper<IOleInPlaceFrame>
|
||||
{
|
||||
JuceOleInPlaceFrame (HWND hwnd) : window (hwnd) {}
|
||||
|
||||
JUCE_COMRESULT GetWindow (HWND* lphwnd) override { *lphwnd = window; return S_OK; }
|
||||
JUCE_COMRESULT ContextSensitiveHelp (BOOL) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT GetBorder (LPRECT) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT RequestBorderSpace (LPCBORDERWIDTHS) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT SetBorderSpace (LPCBORDERWIDTHS) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT SetActiveObject (IOleInPlaceActiveObject* a, LPCOLESTR) override { activeObject = addComSmartPtrOwner (a); return S_OK; }
|
||||
JUCE_COMRESULT InsertMenus (HMENU, LPOLEMENUGROUPWIDTHS) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT SetMenu (HMENU, HOLEMENU, HWND) override { return S_OK; }
|
||||
JUCE_COMRESULT RemoveMenus (HMENU) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT SetStatusText (LPCOLESTR) override { return S_OK; }
|
||||
JUCE_COMRESULT EnableModeless (BOOL) override { return S_OK; }
|
||||
JUCE_COMRESULT TranslateAccelerator (LPMSG, WORD) override { return E_NOTIMPL; }
|
||||
|
||||
HRESULT OfferKeyTranslation (LPMSG lpmsg)
|
||||
{
|
||||
if (activeObject != nullptr)
|
||||
return activeObject->TranslateAcceleratorW (lpmsg);
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
HWND window;
|
||||
ComSmartPtr<IOleInPlaceActiveObject> activeObject;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct JuceIOleInPlaceSite final : public ComBaseClassHelper<IOleInPlaceSite>
|
||||
{
|
||||
JuceIOleInPlaceSite (HWND hwnd)
|
||||
: window (hwnd),
|
||||
frame (new JuceOleInPlaceFrame (window))
|
||||
{}
|
||||
|
||||
~JuceIOleInPlaceSite() override
|
||||
{
|
||||
frame->Release();
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetWindow (HWND* lphwnd) override { *lphwnd = window; return S_OK; }
|
||||
JUCE_COMRESULT ContextSensitiveHelp (BOOL) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT CanInPlaceActivate() override { return S_OK; }
|
||||
JUCE_COMRESULT OnInPlaceActivate() override { return S_OK; }
|
||||
JUCE_COMRESULT OnUIActivate() override { return S_OK; }
|
||||
|
||||
JUCE_COMRESULT GetWindowContext (LPOLEINPLACEFRAME* lplpFrame, LPOLEINPLACEUIWINDOW* lplpDoc, LPRECT, LPRECT, LPOLEINPLACEFRAMEINFO lpFrameInfo) override
|
||||
{
|
||||
/* Note: If you call AddRef on the frame here, then some types of object (e.g. web browser control) cause leaks..
|
||||
If you don't call AddRef then others crash (e.g. QuickTime).. Bit of a catch-22, so letting it leak is probably preferable.
|
||||
*/
|
||||
if (lplpFrame != nullptr) { frame->AddRef(); *lplpFrame = frame; }
|
||||
if (lplpDoc != nullptr) *lplpDoc = nullptr;
|
||||
lpFrameInfo->fMDIApp = FALSE;
|
||||
lpFrameInfo->hwndFrame = window;
|
||||
lpFrameInfo->haccel = nullptr;
|
||||
lpFrameInfo->cAccelEntries = 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Scroll (SIZE) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT OnUIDeactivate (BOOL) override { return S_OK; }
|
||||
JUCE_COMRESULT OnInPlaceDeactivate() override { return S_OK; }
|
||||
JUCE_COMRESULT DiscardUndoState() override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT DeactivateAndUndo() override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT OnPosRectChange (LPCRECT) override { return S_OK; }
|
||||
|
||||
LRESULT offerEventToActiveXControl (::MSG& msg)
|
||||
{
|
||||
if (frame != nullptr)
|
||||
return frame->OfferKeyTranslation (&msg);
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
HWND window;
|
||||
JuceOleInPlaceFrame* frame;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct JuceIOleClientSite final : public ComBaseClassHelper<IOleClientSite>
|
||||
{
|
||||
JuceIOleClientSite (HWND window) : inplaceSite (new JuceIOleInPlaceSite (window))
|
||||
{}
|
||||
|
||||
~JuceIOleClientSite() override
|
||||
{
|
||||
inplaceSite->Release();
|
||||
|
||||
if (dispatchEventHandler != nullptr)
|
||||
{
|
||||
dispatchEventHandler->Release();
|
||||
dispatchEventHandler = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_COMRESULT QueryInterface (REFIID type, void** result) override
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
if (type == __uuidof (IOleInPlaceSite))
|
||||
{
|
||||
inplaceSite->AddRef();
|
||||
*result = static_cast<IOleInPlaceSite*> (inplaceSite);
|
||||
return S_OK;
|
||||
}
|
||||
else if (type == __uuidof (IDispatch) && dispatchEventHandler != nullptr)
|
||||
{
|
||||
dispatchEventHandler->AddRef();
|
||||
*result = dispatchEventHandler;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return ComBaseClassHelper <IOleClientSite>::QueryInterface (type, result);
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
JUCE_COMRESULT SaveObject() override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT GetMoniker (DWORD, DWORD, IMoniker**) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT GetContainer (LPOLECONTAINER* ppContainer) override { *ppContainer = nullptr; return E_NOINTERFACE; }
|
||||
JUCE_COMRESULT ShowObject() override { return S_OK; }
|
||||
JUCE_COMRESULT OnShowWindow (BOOL) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT RequestNewObjectLayout() override { return E_NOTIMPL; }
|
||||
|
||||
LRESULT offerEventToActiveXControl (::MSG& msg)
|
||||
{
|
||||
if (inplaceSite != nullptr)
|
||||
return inplaceSite->offerEventToActiveXControl (msg);
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
void setEventHandler (void* eventHandler)
|
||||
{
|
||||
IDispatch* newEventHandler = nullptr;
|
||||
|
||||
if (eventHandler != nullptr)
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
auto iidIDispatch = __uuidof (IDispatch);
|
||||
|
||||
if (static_cast<IUnknown*> (eventHandler)->QueryInterface (iidIDispatch, (void**) &newEventHandler) != S_OK
|
||||
|| newEventHandler == nullptr)
|
||||
return;
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
if (dispatchEventHandler != nullptr)
|
||||
dispatchEventHandler->Release();
|
||||
|
||||
dispatchEventHandler = newEventHandler;
|
||||
}
|
||||
|
||||
JuceIOleInPlaceSite* inplaceSite;
|
||||
IDispatch* dispatchEventHandler = nullptr;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
static Array<ActiveXControlComponent*> activeXComps;
|
||||
|
||||
static HWND getHWND (const ActiveXControlComponent* const component)
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
HWND hwnd = {};
|
||||
const IID iid = __uuidof (IOleWindow);
|
||||
|
||||
if (auto* window = (IOleWindow*) component->queryInterface (&iid))
|
||||
{
|
||||
window->GetWindow (&hwnd);
|
||||
window->Release();
|
||||
}
|
||||
|
||||
return hwnd;
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
static void offerActiveXMouseEventToPeer (ComponentPeer* peer, HWND hwnd, UINT message, LPARAM lParam)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case WM_MOUSEMOVE:
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_MBUTTONDOWN:
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_LBUTTONUP:
|
||||
case WM_MBUTTONUP:
|
||||
case WM_RBUTTONUP:
|
||||
{
|
||||
RECT activeXRect, peerRect;
|
||||
GetWindowRect (hwnd, &activeXRect);
|
||||
GetWindowRect ((HWND) peer->getNativeHandle(), &peerRect);
|
||||
|
||||
peer->handleMouseEvent (MouseInputSource::InputSourceType::mouse,
|
||||
{ (float) (GET_X_LPARAM (lParam) + activeXRect.left - peerRect.left),
|
||||
(float) (GET_Y_LPARAM (lParam) + activeXRect.top - peerRect.top) },
|
||||
ComponentPeer::getCurrentModifiersRealtime(),
|
||||
MouseInputSource::defaultPressure,
|
||||
MouseInputSource::defaultOrientation,
|
||||
getMouseEventTime());
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class ActiveXControlComponent::Pimpl : public ComponentMovementWatcher,
|
||||
public ComponentPeer::ScaleFactorListener
|
||||
{
|
||||
public:
|
||||
Pimpl (HWND hwnd, ActiveXControlComponent& activeXComp)
|
||||
: ComponentMovementWatcher (&activeXComp),
|
||||
owner (activeXComp),
|
||||
storage (new ActiveXHelpers::JuceIStorage()),
|
||||
clientSite (new ActiveXHelpers::JuceIOleClientSite (hwnd))
|
||||
{
|
||||
}
|
||||
|
||||
~Pimpl() override
|
||||
{
|
||||
// If the wndproc of the ActiveX HWND isn't set back to it's original
|
||||
// wndproc, then clientSite will leak when control is released
|
||||
if (controlHWND != nullptr)
|
||||
SetWindowLongPtr ((HWND) controlHWND, GWLP_WNDPROC, (LONG_PTR) originalWndProc);
|
||||
|
||||
if (control != nullptr)
|
||||
{
|
||||
control->Close (OLECLOSE_NOSAVE);
|
||||
control->Release();
|
||||
}
|
||||
|
||||
clientSite->Release();
|
||||
storage->Release();
|
||||
|
||||
if (currentPeer != nullptr)
|
||||
currentPeer->removeScaleFactorListener (this);
|
||||
}
|
||||
|
||||
void setControlBounds (Rectangle<int> newBounds) const
|
||||
{
|
||||
if (controlHWND != nullptr)
|
||||
{
|
||||
if (auto* peer = owner.getTopLevelComponent()->getPeer())
|
||||
newBounds = (newBounds.toDouble() * peer->getPlatformScaleFactor()).toNearestInt();
|
||||
|
||||
MoveWindow (controlHWND, newBounds.getX(), newBounds.getY(), newBounds.getWidth(), newBounds.getHeight(), TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
void setControlVisible (bool shouldBeVisible) const
|
||||
{
|
||||
if (controlHWND != nullptr)
|
||||
ShowWindow (controlHWND, shouldBeVisible ? SW_SHOWNA : SW_HIDE);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
using ComponentMovementWatcher::componentMovedOrResized;
|
||||
|
||||
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
|
||||
{
|
||||
if (auto* peer = owner.getTopLevelComponent()->getPeer())
|
||||
setControlBounds (peer->getAreaCoveredBy (owner));
|
||||
}
|
||||
|
||||
void componentPeerChanged() override
|
||||
{
|
||||
if (currentPeer != nullptr)
|
||||
currentPeer->removeScaleFactorListener (this);
|
||||
|
||||
componentMovedOrResized (true, true);
|
||||
|
||||
currentPeer = owner.getTopLevelComponent()->getPeer();
|
||||
|
||||
if (currentPeer != nullptr)
|
||||
currentPeer->addScaleFactorListener (this);
|
||||
}
|
||||
|
||||
using ComponentMovementWatcher::componentVisibilityChanged;
|
||||
|
||||
void componentVisibilityChanged() override
|
||||
{
|
||||
setControlVisible (owner.isShowing());
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
void nativeScaleFactorChanged (double /*newScaleFactor*/) override
|
||||
{
|
||||
componentMovedOrResized (true, true);
|
||||
}
|
||||
|
||||
// intercepts events going to an activeX control, so we can sneakily use the mouse events
|
||||
static LRESULT CALLBACK activeXHookWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
for (auto* ax : ActiveXHelpers::activeXComps)
|
||||
{
|
||||
if (ax->control != nullptr && ax->control->controlHWND == hwnd)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case WM_MOUSEMOVE:
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_MBUTTONDOWN:
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_LBUTTONUP:
|
||||
case WM_MBUTTONUP:
|
||||
case WM_RBUTTONUP:
|
||||
case WM_LBUTTONDBLCLK:
|
||||
case WM_MBUTTONDBLCLK:
|
||||
case WM_RBUTTONDBLCLK:
|
||||
if (ax->isShowing())
|
||||
{
|
||||
if (auto* peer = ax->getPeer())
|
||||
{
|
||||
ActiveXHelpers::offerActiveXMouseEventToPeer (peer, hwnd, message, lParam);
|
||||
|
||||
if (! ax->areMouseEventsAllowed())
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return CallWindowProc (ax->control->originalWndProc, hwnd, message, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
return DefWindowProc (hwnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
ActiveXControlComponent& owner;
|
||||
ComponentPeer* currentPeer = nullptr;
|
||||
HWND controlHWND = {};
|
||||
IStorage* storage = nullptr;
|
||||
ActiveXHelpers::JuceIOleClientSite* clientSite = nullptr;
|
||||
IOleObject* control = nullptr;
|
||||
WNDPROC originalWndProc = nullptr;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ActiveXControlComponent::ActiveXControlComponent()
|
||||
{
|
||||
ActiveXHelpers::activeXComps.add (this);
|
||||
}
|
||||
|
||||
ActiveXControlComponent::~ActiveXControlComponent()
|
||||
{
|
||||
deleteControl();
|
||||
ActiveXHelpers::activeXComps.removeFirstMatchingValue (this);
|
||||
}
|
||||
|
||||
void ActiveXControlComponent::paint (Graphics& g)
|
||||
{
|
||||
if (control == nullptr)
|
||||
g.fillAll (Colours::lightgrey);
|
||||
}
|
||||
|
||||
bool ActiveXControlComponent::createControl (const void* controlIID)
|
||||
{
|
||||
deleteControl();
|
||||
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
auto controlBounds = peer->getAreaCoveredBy (*this);
|
||||
auto hwnd = (HWND) peer->getNativeHandle();
|
||||
|
||||
std::unique_ptr<Pimpl> newControl (new Pimpl (hwnd, *this));
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
HRESULT hr = OleCreate (*(const IID*) controlIID, __uuidof (IOleObject), 1 /*OLERENDER_DRAW*/, nullptr,
|
||||
newControl->clientSite, newControl->storage,
|
||||
(void**) &(newControl->control));
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
if (hr == S_OK)
|
||||
{
|
||||
newControl->control->SetHostNames (L"JUCE", nullptr);
|
||||
|
||||
if (OleSetContainedObject (newControl->control, TRUE) == S_OK)
|
||||
{
|
||||
RECT rect;
|
||||
rect.left = controlBounds.getX();
|
||||
rect.top = controlBounds.getY();
|
||||
rect.right = controlBounds.getRight();
|
||||
rect.bottom = controlBounds.getBottom();
|
||||
|
||||
if (newControl->control->DoVerb (OLEIVERB_SHOW, nullptr, newControl->clientSite, 0, hwnd, &rect) == S_OK)
|
||||
{
|
||||
control = std::move (newControl);
|
||||
control->controlHWND = ActiveXHelpers::getHWND (this);
|
||||
|
||||
if (control->controlHWND != nullptr)
|
||||
{
|
||||
control->setControlBounds (controlBounds);
|
||||
|
||||
control->originalWndProc = (WNDPROC) GetWindowLongPtr ((HWND) control->controlHWND, GWLP_WNDPROC);
|
||||
SetWindowLongPtr ((HWND) control->controlHWND, GWLP_WNDPROC, (LONG_PTR) Pimpl::activeXHookWndProc);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the component must have already been added to a real window when you call this!
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ActiveXControlComponent::deleteControl()
|
||||
{
|
||||
control = nullptr;
|
||||
}
|
||||
|
||||
void* ActiveXControlComponent::queryInterface (const void* iid) const
|
||||
{
|
||||
void* result = nullptr;
|
||||
|
||||
if (control != nullptr && control->control != nullptr
|
||||
&& SUCCEEDED (control->control->QueryInterface (*(const IID*) iid, &result)))
|
||||
return result;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ActiveXControlComponent::setMouseEventsAllowed (const bool eventsCanReachControl)
|
||||
{
|
||||
mouseEventsAllowed = eventsCanReachControl;
|
||||
}
|
||||
|
||||
intptr_t ActiveXControlComponent::offerEventToActiveXControl (void* ptr)
|
||||
{
|
||||
if (control != nullptr && control->clientSite != nullptr)
|
||||
return (intptr_t) control->clientSite->offerEventToActiveXControl (*reinterpret_cast<::MSG*> (ptr));
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
intptr_t ActiveXControlComponent::offerEventToActiveXControlStatic (void* ptr)
|
||||
{
|
||||
for (auto* ax : ActiveXHelpers::activeXComps)
|
||||
{
|
||||
auto result = ax->offerEventToActiveXControl (ptr);
|
||||
|
||||
if (result != S_FALSE)
|
||||
return result;
|
||||
}
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
void ActiveXControlComponent::setEventHandler (void* eventHandler)
|
||||
{
|
||||
if (control->clientSite != nullptr)
|
||||
control->clientSite->setEventHandler (eventHandler);
|
||||
}
|
||||
|
||||
LRESULT juce_offerEventToActiveXControl (::MSG& msg);
|
||||
LRESULT juce_offerEventToActiveXControl (::MSG& msg)
|
||||
{
|
||||
if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)
|
||||
return ActiveXControlComponent::offerEventToActiveXControlStatic (&msg);
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class AndroidViewComponent::Pimpl : public ComponentMovementWatcher
|
||||
{
|
||||
public:
|
||||
Pimpl (const LocalRef<jobject>& v, Component& comp)
|
||||
: ComponentMovementWatcher (&comp),
|
||||
view (v),
|
||||
owner (comp)
|
||||
{
|
||||
if (owner.isShowing())
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
~Pimpl() override
|
||||
{
|
||||
removeFromParent();
|
||||
}
|
||||
|
||||
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
|
||||
{
|
||||
auto* topComp = owner.getTopLevelComponent();
|
||||
|
||||
if (topComp->getPeer() != nullptr)
|
||||
{
|
||||
auto pos = topComp->getLocalPoint (&owner, Point<int>());
|
||||
|
||||
Rectangle<int> r (pos.x, pos.y, owner.getWidth(), owner.getHeight());
|
||||
r *= Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale;
|
||||
|
||||
getEnv()->CallVoidMethod (view, AndroidView.layout, r.getX(), r.getY(),
|
||||
r.getRight(), r.getBottom());
|
||||
}
|
||||
}
|
||||
|
||||
void componentPeerChanged() override
|
||||
{
|
||||
auto* peer = owner.getPeer();
|
||||
|
||||
if (currentPeer != peer)
|
||||
{
|
||||
removeFromParent();
|
||||
currentPeer = peer;
|
||||
|
||||
addToParent();
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
VISIBLE = 0,
|
||||
INVISIBLE = 4
|
||||
};
|
||||
|
||||
getEnv()->CallVoidMethod (view, AndroidView.setVisibility, owner.isShowing() ? VISIBLE : INVISIBLE);
|
||||
}
|
||||
|
||||
void componentVisibilityChanged() override
|
||||
{
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
void componentBroughtToFront (Component& comp) override
|
||||
{
|
||||
ComponentMovementWatcher::componentBroughtToFront (comp);
|
||||
}
|
||||
|
||||
Rectangle<int> getViewBounds() const
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
int width = env->CallIntMethod (view, AndroidView.getWidth);
|
||||
int height = env->CallIntMethod (view, AndroidView.getHeight);
|
||||
|
||||
return Rectangle<int> (width, height);
|
||||
}
|
||||
|
||||
GlobalRef view;
|
||||
|
||||
private:
|
||||
void addToParent()
|
||||
{
|
||||
if (currentPeer != nullptr)
|
||||
{
|
||||
jobject peerView = (jobject) currentPeer->getNativeHandle();
|
||||
|
||||
// NB: Assuming a parent is always of ViewGroup type
|
||||
auto* env = getEnv();
|
||||
|
||||
env->CallVoidMethod (peerView, AndroidViewGroup.addView, view.get());
|
||||
componentMovedOrResized (false, false);
|
||||
}
|
||||
}
|
||||
|
||||
void removeFromParent()
|
||||
{
|
||||
auto* env = getEnv();
|
||||
auto parentView = env->CallObjectMethod (view, AndroidView.getParent);
|
||||
|
||||
if (parentView != nullptr)
|
||||
{
|
||||
// Assuming a parent is always of ViewGroup type
|
||||
env->CallVoidMethod (parentView, AndroidViewGroup.removeView, view.get());
|
||||
}
|
||||
}
|
||||
|
||||
Component& owner;
|
||||
ComponentPeer* currentPeer = nullptr;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
AndroidViewComponent::AndroidViewComponent()
|
||||
{
|
||||
}
|
||||
|
||||
AndroidViewComponent::~AndroidViewComponent()
|
||||
{
|
||||
AccessibilityHandler::setNativeChildForComponent (*this, nullptr);
|
||||
}
|
||||
|
||||
void AndroidViewComponent::setView (void* view)
|
||||
{
|
||||
if (view != getView())
|
||||
{
|
||||
pimpl.reset();
|
||||
|
||||
if (view != nullptr)
|
||||
{
|
||||
// explicitly create a new local ref here so that we don't
|
||||
// delete the users pointer
|
||||
auto* env = getEnv();
|
||||
auto localref = LocalRef<jobject> (env->NewLocalRef ((jobject) view));
|
||||
|
||||
pimpl.reset (new Pimpl (localref, *this));
|
||||
|
||||
AccessibilityHandler::setNativeChildForComponent (*this, getView());
|
||||
}
|
||||
else
|
||||
{
|
||||
AccessibilityHandler::setNativeChildForComponent (*this, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void* AndroidViewComponent::getView() const
|
||||
{
|
||||
return pimpl == nullptr ? nullptr : (void*) pimpl->view;
|
||||
}
|
||||
|
||||
void AndroidViewComponent::resizeToFitView()
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
setBounds (pimpl->getViewBounds());
|
||||
}
|
||||
|
||||
void AndroidViewComponent::paint (Graphics&) {}
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> AndroidViewComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
AppleRemoteDevice::AppleRemoteDevice()
|
||||
: device (nullptr),
|
||||
queue (nullptr),
|
||||
remoteId (0)
|
||||
{
|
||||
}
|
||||
|
||||
AppleRemoteDevice::~AppleRemoteDevice()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
io_object_t getAppleRemoteDevice()
|
||||
{
|
||||
CFMutableDictionaryRef dict = IOServiceMatching ("AppleIRController");
|
||||
|
||||
io_iterator_t iter = 0;
|
||||
io_object_t iod = 0;
|
||||
|
||||
const auto defaultPort = []
|
||||
{
|
||||
#if JUCE_MAC_API_VERSION_CAN_BE_BUILT (12, 0)
|
||||
if (@available (macOS 12.0, *))
|
||||
return kIOMainPortDefault;
|
||||
#endif
|
||||
|
||||
JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS
|
||||
return kIOMasterPortDefault;
|
||||
JUCE_END_IGNORE_DEPRECATION_WARNINGS
|
||||
}();
|
||||
|
||||
if (IOServiceGetMatchingServices (defaultPort, dict, &iter) == kIOReturnSuccess
|
||||
&& iter != 0)
|
||||
{
|
||||
iod = IOIteratorNext (iter);
|
||||
}
|
||||
|
||||
IOObjectRelease (iter);
|
||||
return iod;
|
||||
}
|
||||
|
||||
bool createAppleRemoteInterface (io_object_t iod, void** device)
|
||||
{
|
||||
jassert (*device == nullptr);
|
||||
io_name_t classname;
|
||||
|
||||
if (IOObjectGetClass (iod, classname) == kIOReturnSuccess)
|
||||
{
|
||||
IOCFPlugInInterface** cfPlugInInterface = nullptr;
|
||||
SInt32 score = 0;
|
||||
|
||||
if (IOCreatePlugInInterfaceForService (iod,
|
||||
kIOHIDDeviceUserClientTypeID,
|
||||
kIOCFPlugInInterfaceID,
|
||||
&cfPlugInInterface,
|
||||
&score) == kIOReturnSuccess)
|
||||
{
|
||||
[[maybe_unused]] HRESULT hr = (*cfPlugInInterface)->QueryInterface (cfPlugInInterface,
|
||||
CFUUIDGetUUIDBytes (kIOHIDDeviceInterfaceID),
|
||||
device);
|
||||
|
||||
(*cfPlugInInterface)->Release (cfPlugInInterface);
|
||||
}
|
||||
}
|
||||
|
||||
return *device != nullptr;
|
||||
}
|
||||
|
||||
void appleRemoteQueueCallback (void* const target, const IOReturn result, void*, void*)
|
||||
{
|
||||
if (result == kIOReturnSuccess)
|
||||
((AppleRemoteDevice*) target)->handleCallbackInternal();
|
||||
}
|
||||
}
|
||||
|
||||
bool AppleRemoteDevice::start (const bool inExclusiveMode)
|
||||
{
|
||||
if (queue != nullptr)
|
||||
return true;
|
||||
|
||||
stop();
|
||||
|
||||
bool result = false;
|
||||
io_object_t iod = getAppleRemoteDevice();
|
||||
|
||||
if (iod != 0)
|
||||
{
|
||||
if (createAppleRemoteInterface (iod, &device) && open (inExclusiveMode))
|
||||
result = true;
|
||||
else
|
||||
stop();
|
||||
|
||||
IOObjectRelease (iod);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void AppleRemoteDevice::stop()
|
||||
{
|
||||
if (queue != nullptr)
|
||||
{
|
||||
(*(IOHIDQueueInterface**) queue)->stop ((IOHIDQueueInterface**) queue);
|
||||
(*(IOHIDQueueInterface**) queue)->dispose ((IOHIDQueueInterface**) queue);
|
||||
(*(IOHIDQueueInterface**) queue)->Release ((IOHIDQueueInterface**) queue);
|
||||
queue = nullptr;
|
||||
}
|
||||
|
||||
if (device != nullptr)
|
||||
{
|
||||
(*(IOHIDDeviceInterface**) device)->close ((IOHIDDeviceInterface**) device);
|
||||
(*(IOHIDDeviceInterface**) device)->Release ((IOHIDDeviceInterface**) device);
|
||||
device = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool AppleRemoteDevice::isActive() const
|
||||
{
|
||||
return queue != nullptr;
|
||||
}
|
||||
|
||||
bool AppleRemoteDevice::open (const bool openInExclusiveMode)
|
||||
{
|
||||
Array<int> cookies;
|
||||
|
||||
CFObjectHolder<CFArrayRef> elements;
|
||||
auto device122 = (IOHIDDeviceInterface122**) device;
|
||||
|
||||
if ((*device122)->copyMatchingElements (device122, nullptr, &elements.object) != kIOReturnSuccess)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < CFArrayGetCount (elements.object); ++i)
|
||||
{
|
||||
auto element = (CFDictionaryRef) CFArrayGetValueAtIndex (elements.object, i);
|
||||
|
||||
// get the cookie
|
||||
CFTypeRef object = CFDictionaryGetValue (element, CFSTR (kIOHIDElementCookieKey));
|
||||
|
||||
if (object == nullptr || CFGetTypeID (object) != CFNumberGetTypeID())
|
||||
continue;
|
||||
|
||||
long number;
|
||||
if (! CFNumberGetValue ((CFNumberRef) object, kCFNumberLongType, &number))
|
||||
continue;
|
||||
|
||||
cookies.add ((int) number);
|
||||
}
|
||||
|
||||
if ((*(IOHIDDeviceInterface**) device)
|
||||
->open ((IOHIDDeviceInterface**) device,
|
||||
openInExclusiveMode ? kIOHIDOptionsTypeSeizeDevice
|
||||
: kIOHIDOptionsTypeNone) == KERN_SUCCESS)
|
||||
{
|
||||
queue = (*(IOHIDDeviceInterface**) device)->allocQueue ((IOHIDDeviceInterface**) device);
|
||||
|
||||
if (queue != nullptr)
|
||||
{
|
||||
(*(IOHIDQueueInterface**) queue)->create ((IOHIDQueueInterface**) queue, 0, 12);
|
||||
|
||||
for (int i = 0; i < cookies.size(); ++i)
|
||||
{
|
||||
IOHIDElementCookie cookie = (IOHIDElementCookie) cookies.getUnchecked (i);
|
||||
(*(IOHIDQueueInterface**) queue)->addElement ((IOHIDQueueInterface**) queue, cookie, 0);
|
||||
}
|
||||
|
||||
CFRunLoopSourceRef eventSource;
|
||||
|
||||
if ((*(IOHIDQueueInterface**) queue)
|
||||
->createAsyncEventSource ((IOHIDQueueInterface**) queue, &eventSource) == KERN_SUCCESS)
|
||||
{
|
||||
if ((*(IOHIDQueueInterface**) queue)->setEventCallout ((IOHIDQueueInterface**) queue,
|
||||
appleRemoteQueueCallback, this, nullptr) == KERN_SUCCESS)
|
||||
{
|
||||
CFRunLoopAddSource (CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode);
|
||||
|
||||
(*(IOHIDQueueInterface**) queue)->start ((IOHIDQueueInterface**) queue);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AppleRemoteDevice::handleCallbackInternal()
|
||||
{
|
||||
int totalValues = 0;
|
||||
AbsoluteTime nullTime = { 0, 0 };
|
||||
char cookies [12];
|
||||
int numCookies = 0;
|
||||
|
||||
while (numCookies < numElementsInArray (cookies))
|
||||
{
|
||||
IOHIDEventStruct e;
|
||||
|
||||
if ((*(IOHIDQueueInterface**) queue)->getNextEvent ((IOHIDQueueInterface**) queue, &e, nullTime, 0) != kIOReturnSuccess)
|
||||
break;
|
||||
|
||||
if ((int) e.elementCookie == 19)
|
||||
{
|
||||
remoteId = e.value;
|
||||
buttonPressed (switched, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
totalValues += e.value;
|
||||
cookies [numCookies++] = (char) (pointer_sized_int) e.elementCookie;
|
||||
}
|
||||
}
|
||||
|
||||
cookies [numCookies++] = 0;
|
||||
|
||||
static const char buttonPatterns[] =
|
||||
{
|
||||
0x1f, 0x14, 0x12, 0x1f, 0x14, 0x12, 0,
|
||||
0x1f, 0x15, 0x12, 0x1f, 0x15, 0x12, 0,
|
||||
0x1f, 0x1d, 0x1c, 0x12, 0,
|
||||
0x1f, 0x1e, 0x1c, 0x12, 0,
|
||||
0x1f, 0x16, 0x12, 0x1f, 0x16, 0x12, 0,
|
||||
0x1f, 0x17, 0x12, 0x1f, 0x17, 0x12, 0,
|
||||
0x1f, 0x12, 0x04, 0x02, 0,
|
||||
0x1f, 0x12, 0x03, 0x02, 0,
|
||||
0x1f, 0x12, 0x1f, 0x12, 0,
|
||||
0x23, 0x1f, 0x12, 0x23, 0x1f, 0x12, 0,
|
||||
19, 0
|
||||
};
|
||||
|
||||
int buttonNum = (int) menuButton;
|
||||
int i = 0;
|
||||
|
||||
while (i < numElementsInArray (buttonPatterns))
|
||||
{
|
||||
if (strcmp (cookies, buttonPatterns + i) == 0)
|
||||
{
|
||||
buttonPressed ((ButtonType) buttonNum, totalValues > 0);
|
||||
break;
|
||||
}
|
||||
|
||||
i += (int) strlen (buttonPatterns + i) + 1;
|
||||
++buttonNum;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class HWNDComponent::Pimpl : public ComponentMovementWatcher
|
||||
{
|
||||
public:
|
||||
Pimpl (HWND h, Component& comp)
|
||||
: ComponentMovementWatcher (&comp),
|
||||
hwnd (h),
|
||||
owner (comp)
|
||||
{
|
||||
if (owner.isShowing())
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
~Pimpl() override
|
||||
{
|
||||
removeFromParent();
|
||||
DestroyWindow (hwnd);
|
||||
}
|
||||
|
||||
void componentMovedOrResized (bool wasMoved, bool wasResized) override
|
||||
{
|
||||
if (auto* peer = owner.getTopLevelComponent()->getPeer())
|
||||
{
|
||||
auto area = (peer->getAreaCoveredBy (owner).toFloat() * peer->getPlatformScaleFactor()).getSmallestIntegerContainer();
|
||||
|
||||
UINT flagsToSend = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER;
|
||||
|
||||
if (! wasMoved) flagsToSend |= SWP_NOMOVE;
|
||||
if (! wasResized) flagsToSend |= SWP_NOSIZE;
|
||||
|
||||
ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { hwnd };
|
||||
|
||||
SetWindowPos (hwnd, nullptr, area.getX(), area.getY(), area.getWidth(), area.getHeight(), flagsToSend);
|
||||
|
||||
invalidateHWNDAndChildren();
|
||||
}
|
||||
}
|
||||
|
||||
using ComponentMovementWatcher::componentMovedOrResized;
|
||||
|
||||
void componentPeerChanged() override
|
||||
{
|
||||
auto* peer = owner.getPeer();
|
||||
|
||||
if (currentPeer != peer)
|
||||
{
|
||||
removeFromParent();
|
||||
currentPeer = peer;
|
||||
|
||||
addToParent();
|
||||
}
|
||||
|
||||
auto isShowing = owner.isShowing();
|
||||
|
||||
ShowWindow (hwnd, isShowing ? SW_SHOWNA : SW_HIDE);
|
||||
|
||||
if (isShowing)
|
||||
InvalidateRect (hwnd, nullptr, TRUE);
|
||||
}
|
||||
|
||||
void componentVisibilityChanged() override
|
||||
{
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
using ComponentMovementWatcher::componentVisibilityChanged;
|
||||
|
||||
void componentBroughtToFront (Component& comp) override
|
||||
{
|
||||
ComponentMovementWatcher::componentBroughtToFront (comp);
|
||||
}
|
||||
|
||||
Rectangle<int> getHWNDBounds() const
|
||||
{
|
||||
if (auto* peer = owner.getPeer())
|
||||
{
|
||||
ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { hwnd };
|
||||
|
||||
RECT r;
|
||||
GetWindowRect (hwnd, &r);
|
||||
Rectangle<int> windowRectangle (r.right - r.left, r.bottom - r.top);
|
||||
|
||||
return (windowRectangle.toFloat() / peer->getPlatformScaleFactor()).toNearestInt();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void invalidateHWNDAndChildren()
|
||||
{
|
||||
EnumChildWindows (hwnd, invalidateHwndCallback, 0);
|
||||
}
|
||||
|
||||
static BOOL WINAPI invalidateHwndCallback (HWND hwnd, LPARAM)
|
||||
{
|
||||
InvalidateRect (hwnd, nullptr, TRUE);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
HWND hwnd;
|
||||
|
||||
private:
|
||||
void addToParent()
|
||||
{
|
||||
if (currentPeer != nullptr)
|
||||
{
|
||||
auto windowFlags = GetWindowLongPtr (hwnd, GWL_STYLE);
|
||||
|
||||
using FlagType = decltype (windowFlags);
|
||||
|
||||
windowFlags &= ~(FlagType) WS_POPUP;
|
||||
windowFlags |= (FlagType) WS_CHILD;
|
||||
|
||||
SetWindowLongPtr (hwnd, GWL_STYLE, windowFlags);
|
||||
SetParent (hwnd, (HWND) currentPeer->getNativeHandle());
|
||||
|
||||
componentMovedOrResized (true, true);
|
||||
}
|
||||
}
|
||||
|
||||
void removeFromParent()
|
||||
{
|
||||
ShowWindow (hwnd, SW_HIDE);
|
||||
SetParent (hwnd, nullptr);
|
||||
}
|
||||
|
||||
Component& owner;
|
||||
ComponentPeer* currentPeer = nullptr;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
HWNDComponent::HWNDComponent() {}
|
||||
HWNDComponent::~HWNDComponent() {}
|
||||
|
||||
void HWNDComponent::paint (Graphics&) {}
|
||||
|
||||
void HWNDComponent::setHWND (void* hwnd)
|
||||
{
|
||||
if (hwnd != getHWND())
|
||||
{
|
||||
pimpl.reset();
|
||||
|
||||
if (hwnd != nullptr)
|
||||
pimpl.reset (new Pimpl ((HWND) hwnd, *this));
|
||||
}
|
||||
}
|
||||
|
||||
void* HWNDComponent::getHWND() const
|
||||
{
|
||||
return pimpl == nullptr ? nullptr : (void*) pimpl->hwnd;
|
||||
}
|
||||
|
||||
void HWNDComponent::resizeToFit()
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
setBounds (pimpl->getHWNDBounds());
|
||||
}
|
||||
|
||||
void HWNDComponent::updateHWNDBounds()
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
pimpl->componentMovedOrResized (true, true);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class NSViewAttachment final : public ReferenceCountedObject,
|
||||
public ComponentMovementWatcher
|
||||
{
|
||||
public:
|
||||
NSViewAttachment (NSView* v, Component& comp)
|
||||
: ComponentMovementWatcher (&comp),
|
||||
view (v), owner (comp)
|
||||
{
|
||||
[view retain];
|
||||
[view setPostsFrameChangedNotifications: YES];
|
||||
updateAlpha();
|
||||
|
||||
if (owner.isShowing())
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
~NSViewAttachment() override
|
||||
{
|
||||
removeFromParent();
|
||||
[view release];
|
||||
}
|
||||
|
||||
void componentMovedOrResized (Component& comp, bool wasMoved, bool wasResized) override
|
||||
{
|
||||
ComponentMovementWatcher::componentMovedOrResized (comp, wasMoved, wasResized);
|
||||
|
||||
// The ComponentMovementWatcher version of this method avoids calling
|
||||
// us when the top-level comp is resized, but if we're listening to the
|
||||
// top-level comp we still want the NSView to track its size.
|
||||
if (comp.isOnDesktop() && wasResized)
|
||||
componentMovedOrResized (wasMoved, wasResized);
|
||||
}
|
||||
|
||||
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
|
||||
{
|
||||
if (auto* peer = owner.getTopLevelComponent()->getPeer())
|
||||
{
|
||||
const auto newArea = peer->getAreaCoveredBy (owner);
|
||||
|
||||
if (convertToRectInt ([view frame]) != newArea)
|
||||
[view setFrame: makeCGRect (newArea)];
|
||||
}
|
||||
}
|
||||
|
||||
void componentPeerChanged() override
|
||||
{
|
||||
auto* peer = owner.getPeer();
|
||||
|
||||
if (std::exchange (currentPeer, peer) != peer)
|
||||
{
|
||||
if (peer != nullptr)
|
||||
{
|
||||
auto peerView = (NSView*) peer->getNativeHandle();
|
||||
[peerView addSubview: view];
|
||||
|
||||
componentMovedOrResized (false, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
removeFromParent();
|
||||
}
|
||||
}
|
||||
|
||||
[view setHidden: ! owner.isShowing()];
|
||||
}
|
||||
|
||||
void componentVisibilityChanged() override
|
||||
{
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
void updateAlpha()
|
||||
{
|
||||
[view setAlphaValue: (CGFloat) owner.getAlpha()];
|
||||
}
|
||||
|
||||
NSView* const view;
|
||||
|
||||
using Ptr = ReferenceCountedObjectPtr<NSViewAttachment>;
|
||||
|
||||
private:
|
||||
Component& owner;
|
||||
ComponentPeer* currentPeer = nullptr;
|
||||
NSViewFrameWatcher frameWatcher { view, [this] { owner.childBoundsChanged (nullptr); } };
|
||||
|
||||
void removeFromParent()
|
||||
{
|
||||
if ([view superview] != nil)
|
||||
[view removeFromSuperview]; // Must be careful not to call this unless it's required - e.g. some Apple AU views
|
||||
// override the call and use it as a sign that they're being deleted, which breaks everything..
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewAttachment)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
NSViewComponent::NSViewComponent() = default;
|
||||
NSViewComponent::~NSViewComponent()
|
||||
{
|
||||
AccessibilityHandler::setNativeChildForComponent (*this, nullptr);
|
||||
}
|
||||
|
||||
void NSViewComponent::setView (void* view)
|
||||
{
|
||||
if (view != getView())
|
||||
{
|
||||
auto old = attachment;
|
||||
|
||||
attachment = nullptr;
|
||||
|
||||
if (view != nullptr)
|
||||
attachment = attachViewToComponent (*this, view);
|
||||
|
||||
old = nullptr;
|
||||
|
||||
AccessibilityHandler::setNativeChildForComponent (*this, getView());
|
||||
}
|
||||
}
|
||||
|
||||
void* NSViewComponent::getView() const
|
||||
{
|
||||
return attachment != nullptr ? static_cast<NSViewAttachment*> (attachment.get())->view
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
void NSViewComponent::resizeToFitView()
|
||||
{
|
||||
if (attachment != nullptr)
|
||||
{
|
||||
auto* view = static_cast<NSViewAttachment*> (attachment.get())->view;
|
||||
auto r = [view frame];
|
||||
setBounds (Rectangle<int> ((int) r.size.width, (int) r.size.height));
|
||||
|
||||
if (auto* peer = getTopLevelComponent()->getPeer())
|
||||
{
|
||||
const auto position = peer->getAreaCoveredBy (*this).getPosition();
|
||||
[view setFrameOrigin: convertToCGPoint (position)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NSViewComponent::resizeViewToFit()
|
||||
{
|
||||
if (attachment != nullptr)
|
||||
static_cast<NSViewAttachment*> (attachment.get())->componentMovedOrResized (true, true);
|
||||
}
|
||||
|
||||
void NSViewComponent::paint (Graphics&) {}
|
||||
|
||||
void NSViewComponent::alphaChanged()
|
||||
{
|
||||
if (attachment != nullptr)
|
||||
(static_cast<NSViewAttachment*> (attachment.get()))->updateAlpha();
|
||||
}
|
||||
|
||||
ReferenceCountedObject* NSViewComponent::attachViewToComponent (Component& comp, void* view)
|
||||
{
|
||||
return new NSViewAttachment ((NSView*) view, comp);
|
||||
}
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> NSViewComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if JUCE_MAC
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
const auto nsViewFrameChangedSelector = @selector (frameChanged:);
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
struct NSViewCallbackInterface
|
||||
{
|
||||
virtual ~NSViewCallbackInterface() = default;
|
||||
virtual void frameChanged() = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct NSViewFrameChangeCallbackClass : public ObjCClass<NSObject>
|
||||
{
|
||||
NSViewFrameChangeCallbackClass()
|
||||
: ObjCClass ("JUCE_NSViewCallback_")
|
||||
{
|
||||
addIvar<NSViewCallbackInterface*> ("target");
|
||||
|
||||
addMethod (nsViewFrameChangedSelector, frameChanged);
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void setTarget (id self, NSViewCallbackInterface* c)
|
||||
{
|
||||
object_setInstanceVariable (self, "target", c);
|
||||
}
|
||||
|
||||
private:
|
||||
static void frameChanged (id self, SEL, NSNotification*)
|
||||
{
|
||||
if (auto* target = getIvar<NSViewCallbackInterface*> (self, "target"))
|
||||
target->frameChanged();
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (NSViewFrameChangeCallbackClass)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class NSViewFrameWatcher : private NSViewCallbackInterface
|
||||
{
|
||||
public:
|
||||
NSViewFrameWatcher (NSView* viewToWatch, std::function<void()> viewResizedIn)
|
||||
: viewResized (std::move (viewResizedIn)), callback (makeCallbackForView (viewToWatch))
|
||||
{
|
||||
}
|
||||
|
||||
~NSViewFrameWatcher() override
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver: callback];
|
||||
[callback release];
|
||||
callback = nil;
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (NSViewFrameWatcher)
|
||||
JUCE_DECLARE_NON_MOVEABLE (NSViewFrameWatcher)
|
||||
|
||||
private:
|
||||
id makeCallbackForView (NSView* view)
|
||||
{
|
||||
static NSViewFrameChangeCallbackClass cls;
|
||||
auto* result = [cls.createInstance() init];
|
||||
NSViewFrameChangeCallbackClass::setTarget (result, this);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver: result
|
||||
selector: nsViewFrameChangedSelector
|
||||
name: NSViewFrameDidChangeNotification
|
||||
object: view];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void frameChanged() override { viewResized(); }
|
||||
|
||||
std::function<void()> viewResized;
|
||||
id callback;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,622 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#define JUCE_PUSH_NOTIFICATIONS_IMPL 1
|
||||
|
||||
struct PushNotificationsDelegateDetails
|
||||
{
|
||||
//==============================================================================
|
||||
using Action = PushNotifications::Settings::Action;
|
||||
using Category = PushNotifications::Settings::Category;
|
||||
|
||||
static void* actionToNSAction (const Action& a)
|
||||
{
|
||||
if (a.style == Action::text)
|
||||
{
|
||||
return [UNTextInputNotificationAction actionWithIdentifier: juceStringToNS (a.identifier)
|
||||
title: juceStringToNS (a.title)
|
||||
options: NSUInteger (a.destructive << 1 | (! a.triggerInBackground) << 2)
|
||||
textInputButtonTitle: juceStringToNS (a.textInputButtonText)
|
||||
textInputPlaceholder: juceStringToNS (a.textInputPlaceholder)];
|
||||
}
|
||||
|
||||
return [UNNotificationAction actionWithIdentifier: juceStringToNS (a.identifier)
|
||||
title: juceStringToNS (a.title)
|
||||
options: NSUInteger (a.destructive << 1 | (! a.triggerInBackground) << 2)];
|
||||
}
|
||||
|
||||
static void* categoryToNSCategory (const Category& c)
|
||||
{
|
||||
auto actions = [NSMutableArray arrayWithCapacity: (NSUInteger) c.actions.size()];
|
||||
|
||||
for (const auto& a : c.actions)
|
||||
{
|
||||
auto* action = (UNNotificationAction*) actionToNSAction (a);
|
||||
[actions addObject: action];
|
||||
}
|
||||
|
||||
return [UNNotificationCategory categoryWithIdentifier: juceStringToNS (c.identifier)
|
||||
actions: actions
|
||||
intentIdentifiers: @[]
|
||||
options: c.sendDismissAction ? UNNotificationCategoryOptionCustomDismissAction : 0];
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static UNNotificationRequest* juceNotificationToUNNotificationRequest (const PushNotifications::Notification& n)
|
||||
{
|
||||
// content
|
||||
auto content = [[UNMutableNotificationContent alloc] init];
|
||||
|
||||
content.title = juceStringToNS (n.title);
|
||||
content.subtitle = juceStringToNS (n.subtitle);
|
||||
content.threadIdentifier = juceStringToNS (n.groupId);
|
||||
content.body = juceStringToNS (n.body);
|
||||
content.categoryIdentifier = juceStringToNS (n.category);
|
||||
content.badge = [NSNumber numberWithInt: n.badgeNumber];
|
||||
|
||||
auto soundToPlayString = n.soundToPlay.toString (true);
|
||||
|
||||
if (soundToPlayString == "default_os_sound")
|
||||
content.sound = [UNNotificationSound defaultSound];
|
||||
else if (soundToPlayString.isNotEmpty())
|
||||
content.sound = [UNNotificationSound soundNamed: juceStringToNS (soundToPlayString)];
|
||||
|
||||
auto* propsDict = (NSMutableDictionary*) [varToNSDictionary (n.properties) mutableCopy];
|
||||
[propsDict setObject: juceStringToNS (soundToPlayString) forKey: @"com.juce.soundName"];
|
||||
content.userInfo = propsDict;
|
||||
|
||||
// trigger
|
||||
UNTimeIntervalNotificationTrigger* trigger = nil;
|
||||
|
||||
if (std::abs (n.triggerIntervalSec) >= 0.001)
|
||||
{
|
||||
BOOL shouldRepeat = n.repeat && n.triggerIntervalSec >= 60;
|
||||
trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval: n.triggerIntervalSec repeats: shouldRepeat];
|
||||
}
|
||||
|
||||
// request
|
||||
// each notification needs to have an identifier, otherwise it will not show up
|
||||
jassert (n.identifier.isNotEmpty());
|
||||
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier: juceStringToNS (n.identifier)
|
||||
content: content
|
||||
trigger: trigger];
|
||||
|
||||
[content autorelease];
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
static String getUserResponseFromNSDictionary (NSDictionary* dictionary)
|
||||
{
|
||||
if (dictionary == nil || dictionary.count == 0)
|
||||
return {};
|
||||
|
||||
jassert (dictionary.count == 1);
|
||||
|
||||
for (NSString* key in dictionary)
|
||||
{
|
||||
const auto keyString = nsStringToJuce (key);
|
||||
|
||||
id value = dictionary[key];
|
||||
|
||||
if ([value isKindOfClass: [NSString class]])
|
||||
return nsStringToJuce ((NSString*) value);
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static var getNotificationPropertiesFromDictionaryVar (const var& dictionaryVar)
|
||||
{
|
||||
DynamicObject* dictionaryVarObject = dictionaryVar.getDynamicObject();
|
||||
|
||||
if (dictionaryVarObject == nullptr)
|
||||
return {};
|
||||
|
||||
const auto& properties = dictionaryVarObject->getProperties();
|
||||
|
||||
DynamicObject::Ptr propsVarObject = new DynamicObject();
|
||||
|
||||
for (int i = 0; i < properties.size(); ++i)
|
||||
{
|
||||
auto propertyName = properties.getName (i).toString();
|
||||
|
||||
if (propertyName == "aps")
|
||||
continue;
|
||||
|
||||
propsVarObject->setProperty (propertyName, properties.getValueAt (i));
|
||||
}
|
||||
|
||||
return var (propsVarObject.get());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static double getIntervalSecFromUNNotificationTrigger (UNNotificationTrigger* t)
|
||||
{
|
||||
if (t != nil)
|
||||
{
|
||||
if ([t isKindOfClass: [UNTimeIntervalNotificationTrigger class]])
|
||||
{
|
||||
auto* trigger = (UNTimeIntervalNotificationTrigger*) t;
|
||||
return trigger.timeInterval;
|
||||
}
|
||||
else if ([t isKindOfClass: [UNCalendarNotificationTrigger class]])
|
||||
{
|
||||
auto* trigger = (UNCalendarNotificationTrigger*) t;
|
||||
NSDate* date = [trigger.dateComponents date];
|
||||
NSDate* dateNow = [NSDate date];
|
||||
return [dateNow timeIntervalSinceDate: date];
|
||||
}
|
||||
}
|
||||
|
||||
return 0.;
|
||||
}
|
||||
|
||||
static PushNotifications::Notification unNotificationRequestToJuceNotification (UNNotificationRequest* r)
|
||||
{
|
||||
PushNotifications::Notification n;
|
||||
|
||||
n.identifier = nsStringToJuce (r.identifier);
|
||||
n.title = nsStringToJuce (r.content.title);
|
||||
n.subtitle = nsStringToJuce (r.content.subtitle);
|
||||
n.body = nsStringToJuce (r.content.body);
|
||||
n.groupId = nsStringToJuce (r.content.threadIdentifier);
|
||||
n.category = nsStringToJuce (r.content.categoryIdentifier);
|
||||
n.badgeNumber = r.content.badge.intValue;
|
||||
|
||||
auto userInfoVar = nsDictionaryToVar (r.content.userInfo);
|
||||
|
||||
if (auto* object = userInfoVar.getDynamicObject())
|
||||
{
|
||||
static const Identifier soundName ("com.juce.soundName");
|
||||
n.soundToPlay = URL (object->getProperty (soundName).toString());
|
||||
object->removeProperty (soundName);
|
||||
}
|
||||
|
||||
n.properties = userInfoVar;
|
||||
|
||||
n.triggerIntervalSec = getIntervalSecFromUNNotificationTrigger (r.trigger);
|
||||
n.repeat = r.trigger != nil && r.trigger.repeats;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static PushNotifications::Notification unNotificationToJuceNotification (UNNotification* n)
|
||||
{
|
||||
return unNotificationRequestToJuceNotification (n.request);
|
||||
}
|
||||
|
||||
static Action unNotificationActionToAction (UNNotificationAction* a)
|
||||
{
|
||||
Action action;
|
||||
|
||||
action.identifier = nsStringToJuce (a.identifier);
|
||||
action.title = nsStringToJuce (a.title);
|
||||
action.triggerInBackground = ! (a.options & UNNotificationActionOptionForeground);
|
||||
action.destructive = a.options & UNNotificationActionOptionDestructive;
|
||||
|
||||
if ([a isKindOfClass: [UNTextInputNotificationAction class]])
|
||||
{
|
||||
auto* textAction = (UNTextInputNotificationAction*)a;
|
||||
|
||||
action.style = Action::text;
|
||||
action.textInputButtonText = nsStringToJuce (textAction.textInputButtonTitle);
|
||||
action.textInputPlaceholder = nsStringToJuce (textAction.textInputPlaceholder);
|
||||
}
|
||||
else
|
||||
{
|
||||
action.style = Action::button;
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
static Category unNotificationCategoryToCategory (UNNotificationCategory* c)
|
||||
{
|
||||
Category category;
|
||||
|
||||
category.identifier = nsStringToJuce (c.identifier);
|
||||
category.sendDismissAction = c.options & UNNotificationCategoryOptionCustomDismissAction;
|
||||
|
||||
for (UNNotificationAction* a in c.actions)
|
||||
category.actions.add (unNotificationActionToAction (a));
|
||||
|
||||
return category;
|
||||
}
|
||||
|
||||
static PushNotifications::Notification nsDictionaryToJuceNotification (NSDictionary* dictionary)
|
||||
{
|
||||
const var dictionaryVar = nsDictionaryToVar (dictionary);
|
||||
|
||||
const var apsVar = dictionaryVar.getProperty ("aps", {});
|
||||
|
||||
if (! apsVar.isObject())
|
||||
return {};
|
||||
|
||||
var alertVar = apsVar.getProperty ("alert", {});
|
||||
|
||||
const var titleVar = alertVar.getProperty ("title", {});
|
||||
const var bodyVar = alertVar.isObject() ? alertVar.getProperty ("body", {}) : alertVar;
|
||||
|
||||
const var categoryVar = apsVar.getProperty ("category", {});
|
||||
const var soundVar = apsVar.getProperty ("sound", {});
|
||||
const var badgeVar = apsVar.getProperty ("badge", {});
|
||||
const var threadIdVar = apsVar.getProperty ("thread-id", {});
|
||||
|
||||
PushNotifications::Notification notification;
|
||||
|
||||
notification.title = titleVar .toString();
|
||||
notification.body = bodyVar .toString();
|
||||
notification.groupId = threadIdVar.toString();
|
||||
notification.category = categoryVar.toString();
|
||||
notification.soundToPlay = URL (soundVar.toString());
|
||||
notification.badgeNumber = (int) badgeVar;
|
||||
notification.properties = getNotificationPropertiesFromDictionaryVar (dictionaryVar);
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
private:
|
||||
~PushNotificationsDelegateDetails() = delete;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
bool PushNotifications::Notification::isValid() const noexcept
|
||||
{
|
||||
return title.isNotEmpty() && body.isNotEmpty() && identifier.isNotEmpty() && category.isNotEmpty();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct PushNotifications::Impl
|
||||
{
|
||||
explicit Impl (PushNotifications& p)
|
||||
: owner (p)
|
||||
{
|
||||
Class::setThis (delegate.get(), this);
|
||||
|
||||
auto appDelegate = [[UIApplication sharedApplication] delegate];
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
if ([appDelegate respondsToSelector: @selector (setPushNotificationsDelegateToUse:)])
|
||||
[appDelegate performSelector: @selector (setPushNotificationsDelegateToUse:) withObject: delegate.get()];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
void requestPermissionsWithSettings (const PushNotifications::Settings& settingsToUse)
|
||||
{
|
||||
settings = settingsToUse;
|
||||
|
||||
auto categories = [NSMutableSet setWithCapacity: (NSUInteger) settings.categories.size()];
|
||||
|
||||
for (const auto& c : settings.categories)
|
||||
{
|
||||
auto* category = (UNNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c);
|
||||
[categories addObject: category];
|
||||
}
|
||||
|
||||
UNAuthorizationOptions authOptions = NSUInteger ((bool)settings.allowBadge << 0
|
||||
| (bool)settings.allowSound << 1
|
||||
| (bool)settings.allowAlert << 2);
|
||||
|
||||
[[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories: categories];
|
||||
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions: authOptions
|
||||
completionHandler: ^(BOOL /*granted*/, NSError* /*error*/)
|
||||
{
|
||||
requestSettingsUsed();
|
||||
}];
|
||||
|
||||
[[UIApplication sharedApplication] registerForRemoteNotifications];
|
||||
}
|
||||
|
||||
void requestSettingsUsed()
|
||||
{
|
||||
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:
|
||||
^(UNNotificationSettings* s)
|
||||
{
|
||||
[[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler:
|
||||
^(NSSet<UNNotificationCategory*>* categories)
|
||||
{
|
||||
settings.allowBadge = s.badgeSetting == UNNotificationSettingEnabled;
|
||||
settings.allowSound = s.soundSetting == UNNotificationSettingEnabled;
|
||||
settings.allowAlert = s.alertSetting == UNNotificationSettingEnabled;
|
||||
|
||||
for (UNNotificationCategory* c in categories)
|
||||
settings.categories.add (PushNotificationsDelegateDetails::unNotificationCategoryToCategory (c));
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); });
|
||||
}
|
||||
];
|
||||
}];
|
||||
}
|
||||
|
||||
bool areNotificationsEnabled() const { return true; }
|
||||
|
||||
void sendLocalNotification (const Notification& n)
|
||||
{
|
||||
UNNotificationRequest* request = PushNotificationsDelegateDetails::juceNotificationToUNNotificationRequest (n);
|
||||
|
||||
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest: request
|
||||
withCompletionHandler: ^(NSError* error)
|
||||
{
|
||||
jassert (error == nil);
|
||||
|
||||
if (error != nil)
|
||||
NSLog (nsStringLiteral ("addNotificationRequest error: %@"), error);
|
||||
}];
|
||||
}
|
||||
|
||||
void getDeliveredNotifications() const
|
||||
{
|
||||
[[UNUserNotificationCenter currentNotificationCenter] getDeliveredNotificationsWithCompletionHandler:
|
||||
^(NSArray<UNNotification*>* notifications)
|
||||
{
|
||||
Array<PushNotifications::Notification> notifs;
|
||||
|
||||
for (UNNotification* n in notifications)
|
||||
notifs.add (PushNotificationsDelegateDetails::unNotificationToJuceNotification (n));
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifs); });
|
||||
}];
|
||||
}
|
||||
|
||||
void removeAllDeliveredNotifications()
|
||||
{
|
||||
[[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
|
||||
}
|
||||
|
||||
void removeDeliveredNotification ([[maybe_unused]] const String& identifier)
|
||||
{
|
||||
NSArray<NSString*>* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)];
|
||||
|
||||
[[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers: identifiers];
|
||||
}
|
||||
|
||||
void setupChannels ([[maybe_unused]] const Array<ChannelGroup>& groups, [[maybe_unused]] const Array<Channel>& channels)
|
||||
{
|
||||
}
|
||||
|
||||
void getPendingLocalNotifications() const
|
||||
{
|
||||
[[UNUserNotificationCenter currentNotificationCenter] getPendingNotificationRequestsWithCompletionHandler:
|
||||
^(NSArray<UNNotificationRequest*>* requests)
|
||||
{
|
||||
Array<PushNotifications::Notification> notifs;
|
||||
|
||||
for (UNNotificationRequest* r : requests)
|
||||
notifs.add (PushNotificationsDelegateDetails::unNotificationRequestToJuceNotification (r));
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
|
||||
}];
|
||||
}
|
||||
|
||||
void removePendingLocalNotification (const String& identifier)
|
||||
{
|
||||
NSArray<NSString*>* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)];
|
||||
[[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers: identifiers];
|
||||
}
|
||||
|
||||
void removeAllPendingLocalNotifications()
|
||||
{
|
||||
[[UNUserNotificationCenter currentNotificationCenter] removeAllPendingNotificationRequests];
|
||||
}
|
||||
|
||||
String getDeviceToken()
|
||||
{
|
||||
// You need to call requestPermissionsWithSettings() first.
|
||||
jassert (initialised);
|
||||
|
||||
return deviceToken;
|
||||
}
|
||||
|
||||
void subscribeToTopic ([[maybe_unused]] const String& topic) {}
|
||||
void unsubscribeFromTopic ([[maybe_unused]] const String& topic) {}
|
||||
|
||||
void sendUpstreamMessage ([[maybe_unused]] const String& serverSenderId,
|
||||
[[maybe_unused]] const String& collapseKey,
|
||||
[[maybe_unused]] const String& messageId,
|
||||
[[maybe_unused]] const String& messageType,
|
||||
[[maybe_unused]] int timeToLive,
|
||||
[[maybe_unused]] const StringPairArray& additionalData)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void registeredForRemoteNotifications (NSData* deviceTokenToUse)
|
||||
{
|
||||
deviceToken = [deviceTokenToUse]() -> String
|
||||
{
|
||||
auto length = deviceTokenToUse.length;
|
||||
|
||||
if (auto* buffer = (const unsigned char*) deviceTokenToUse.bytes)
|
||||
{
|
||||
NSMutableString* hexString = [NSMutableString stringWithCapacity: (length * 2)];
|
||||
|
||||
for (NSUInteger i = 0; i < length; ++i)
|
||||
[hexString appendFormat:@"%02x", buffer[i]];
|
||||
|
||||
return nsStringToJuce ([hexString copy]);
|
||||
}
|
||||
|
||||
return {};
|
||||
}();
|
||||
|
||||
initialised = true;
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.deviceTokenRefreshed (deviceToken); });
|
||||
}
|
||||
|
||||
void failedToRegisterForRemoteNotifications ([[maybe_unused]] NSError* error)
|
||||
{
|
||||
deviceToken.clear();
|
||||
}
|
||||
|
||||
void didReceiveRemoteNotification (NSDictionary* userInfo)
|
||||
{
|
||||
auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo);
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, n); });
|
||||
}
|
||||
|
||||
void didReceiveRemoteNotificationFetchCompletionHandler (NSDictionary* userInfo,
|
||||
void (^completionHandler)(UIBackgroundFetchResult result))
|
||||
{
|
||||
didReceiveRemoteNotification (userInfo);
|
||||
completionHandler (UIBackgroundFetchResultNewData);
|
||||
}
|
||||
|
||||
void handleActionForRemoteNotificationCompletionHandler (NSString* actionIdentifier,
|
||||
NSDictionary* userInfo,
|
||||
NSDictionary* responseInfo,
|
||||
void (^completionHandler)())
|
||||
{
|
||||
auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo);
|
||||
auto actionString = nsStringToJuce (actionIdentifier);
|
||||
auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo);
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (false, n, actionString, response); });
|
||||
|
||||
completionHandler();
|
||||
}
|
||||
|
||||
void willPresentNotificationWithCompletionHandler ([[maybe_unused]] UNNotification* notification,
|
||||
void (^completionHandler)(UNNotificationPresentationOptions options))
|
||||
{
|
||||
NSUInteger options = NSUInteger ((int)settings.allowBadge << 0
|
||||
| (int)settings.allowSound << 1
|
||||
| (int)settings.allowAlert << 2);
|
||||
|
||||
completionHandler (options);
|
||||
}
|
||||
|
||||
void didReceiveNotificationResponseWithCompletionHandler (UNNotificationResponse* response,
|
||||
void (^completionHandler)())
|
||||
{
|
||||
const bool remote = [response.notification.request.trigger isKindOfClass: [UNPushNotificationTrigger class]];
|
||||
|
||||
auto actionString = nsStringToJuce (response.actionIdentifier);
|
||||
|
||||
if (actionString == "com.apple.UNNotificationDefaultActionIdentifier")
|
||||
actionString.clear();
|
||||
else if (actionString == "com.apple.UNNotificationDismissActionIdentifier")
|
||||
actionString = "com.juce.NotificationDeleted";
|
||||
|
||||
auto n = PushNotificationsDelegateDetails::unNotificationToJuceNotification (response.notification);
|
||||
|
||||
String responseString;
|
||||
|
||||
if ([response isKindOfClass: [UNTextInputNotificationResponse class]])
|
||||
{
|
||||
UNTextInputNotificationResponse* textResponse = (UNTextInputNotificationResponse*)response;
|
||||
responseString = nsStringToJuce (textResponse.userText);
|
||||
}
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (! remote, n, actionString, responseString); });
|
||||
completionHandler();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct Class final : public ObjCClass<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>>
|
||||
{
|
||||
Class()
|
||||
: ObjCClass ("JucePushNotificationsDelegate_")
|
||||
{
|
||||
addIvar<Impl*> ("self");
|
||||
|
||||
addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), [] (id self, SEL, UIApplication*, NSData* data)
|
||||
{
|
||||
getThis (self).registeredForRemoteNotifications (data);
|
||||
});
|
||||
|
||||
addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), [] (id self, SEL, UIApplication*, NSError* error)
|
||||
{
|
||||
getThis (self).failedToRegisterForRemoteNotifications (error);
|
||||
});
|
||||
|
||||
addMethod (@selector (application:didReceiveRemoteNotification:), [] (id self, SEL, UIApplication*, NSDictionary* userInfo)
|
||||
{
|
||||
getThis (self).didReceiveRemoteNotification (userInfo);
|
||||
});
|
||||
|
||||
addMethod (@selector (application:didReceiveRemoteNotification:fetchCompletionHandler:), [] (id self, SEL, UIApplication*, NSDictionary* userInfo, void (^completionHandler)(UIBackgroundFetchResult result))
|
||||
{
|
||||
getThis (self).didReceiveRemoteNotificationFetchCompletionHandler (userInfo, completionHandler);
|
||||
});
|
||||
|
||||
addMethod (@selector (application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:), [] (id self, SEL, UIApplication*, NSString* actionIdentifier, NSDictionary* userInfo, NSDictionary* responseInfo, void (^completionHandler)())
|
||||
{
|
||||
getThis (self).handleActionForRemoteNotificationCompletionHandler (actionIdentifier, userInfo, responseInfo, completionHandler);
|
||||
});
|
||||
|
||||
addMethod (@selector (userNotificationCenter:willPresentNotification:withCompletionHandler:), [] (id self, SEL, UNUserNotificationCenter*, UNNotification* notification, void (^completionHandler)(UNNotificationPresentationOptions options))
|
||||
{
|
||||
getThis (self).willPresentNotificationWithCompletionHandler (notification, completionHandler);
|
||||
});
|
||||
|
||||
addMethod (@selector (userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:), [] (id self, SEL, UNUserNotificationCenter*, UNNotificationResponse* response, void (^completionHandler)())
|
||||
{
|
||||
getThis (self).didReceiveNotificationResponseWithCompletionHandler (response, completionHandler);
|
||||
});
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static Impl& getThis (id self) { return *getIvar<Impl*> (self, "self"); }
|
||||
static void setThis (id self, Impl* d) { object_setInstanceVariable (self, "self", d); }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
static Class& getClass()
|
||||
{
|
||||
static Class c;
|
||||
return c;
|
||||
}
|
||||
|
||||
NSUniquePtr<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>> delegate { [getClass().createInstance() init] };
|
||||
|
||||
PushNotifications& owner;
|
||||
|
||||
bool initialised = false;
|
||||
String deviceToken;
|
||||
|
||||
PushNotifications::Settings settings;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,541 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#define JUCE_PUSH_NOTIFICATIONS_IMPL 1
|
||||
|
||||
JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS
|
||||
|
||||
namespace PushNotificationsDelegateDetailsOsx
|
||||
{
|
||||
using Action = PushNotifications::Notification::Action;
|
||||
|
||||
//==============================================================================
|
||||
static NSUserNotification* juceNotificationToNSUserNotification (const PushNotifications::Notification& n)
|
||||
{
|
||||
auto notification = [[NSUserNotification alloc] init];
|
||||
|
||||
notification.title = juceStringToNS (n.title);
|
||||
notification.subtitle = juceStringToNS (n.subtitle);
|
||||
notification.informativeText = juceStringToNS (n.body);
|
||||
notification.userInfo = varToNSDictionary (n.properties);
|
||||
|
||||
auto triggerTime = Time::getCurrentTime() + RelativeTime (n.triggerIntervalSec);
|
||||
notification.deliveryDate = [NSDate dateWithTimeIntervalSince1970: (double) triggerTime.toMilliseconds() / 1000.0];
|
||||
|
||||
if (n.repeat && n.triggerIntervalSec >= 60)
|
||||
{
|
||||
auto dateComponents = [[NSDateComponents alloc] init];
|
||||
auto intervalSec = NSInteger (n.triggerIntervalSec);
|
||||
dateComponents.second = intervalSec;
|
||||
dateComponents.nanosecond = NSInteger ((n.triggerIntervalSec - (double) intervalSec) * 1000000000);
|
||||
|
||||
notification.deliveryRepeatInterval = dateComponents;
|
||||
|
||||
[dateComponents autorelease];
|
||||
}
|
||||
|
||||
auto soundToPlayString = n.soundToPlay.toString (true);
|
||||
|
||||
if (soundToPlayString == "default_os_sound")
|
||||
{
|
||||
notification.soundName = NSUserNotificationDefaultSoundName;
|
||||
}
|
||||
else if (soundToPlayString.isNotEmpty())
|
||||
{
|
||||
auto* soundName = juceStringToNS (soundToPlayString.fromLastOccurrenceOf ("/", false, false)
|
||||
.upToLastOccurrenceOf (".", false, false));
|
||||
|
||||
notification.soundName = soundName;
|
||||
}
|
||||
|
||||
notification.hasActionButton = n.actions.size() > 0;
|
||||
|
||||
if (n.actions.size() > 0)
|
||||
notification.actionButtonTitle = juceStringToNS (n.actions.getReference (0).title);
|
||||
|
||||
notification.identifier = juceStringToNS (n.identifier);
|
||||
|
||||
if (n.actions.size() > 0)
|
||||
{
|
||||
notification.hasReplyButton = n.actions.getReference (0).style == Action::text;
|
||||
notification.responsePlaceholder = juceStringToNS (n.actions.getReference (0).textInputPlaceholder);
|
||||
}
|
||||
|
||||
auto* imageDirectory = n.icon.contains ("/")
|
||||
? juceStringToNS (n.icon.upToLastOccurrenceOf ("/", false, true))
|
||||
: [NSString string];
|
||||
|
||||
auto* imageName = juceStringToNS (n.icon.fromLastOccurrenceOf ("/", false, false)
|
||||
.upToLastOccurrenceOf (".", false, false));
|
||||
auto* imageExtension = juceStringToNS (n.icon.fromLastOccurrenceOf (".", false, false));
|
||||
|
||||
NSString* imagePath = nil;
|
||||
|
||||
if ([imageDirectory length] == NSUInteger (0))
|
||||
{
|
||||
imagePath = [[NSBundle mainBundle] pathForResource: imageName
|
||||
ofType: imageExtension];
|
||||
}
|
||||
else
|
||||
{
|
||||
imagePath = [[NSBundle mainBundle] pathForResource: imageName
|
||||
ofType: imageExtension
|
||||
inDirectory: imageDirectory];
|
||||
}
|
||||
|
||||
notification.contentImage = [[NSImage alloc] initWithContentsOfFile: imagePath];
|
||||
|
||||
if (n.actions.size() > 1)
|
||||
{
|
||||
auto additionalActions = [NSMutableArray arrayWithCapacity: (NSUInteger) n.actions.size() - 1];
|
||||
|
||||
for (int a = 1; a < n.actions.size(); ++a)
|
||||
[additionalActions addObject: [NSUserNotificationAction actionWithIdentifier: juceStringToNS (n.actions[a].identifier)
|
||||
title: juceStringToNS (n.actions[a].title)]];
|
||||
|
||||
notification.additionalActions = additionalActions;
|
||||
}
|
||||
|
||||
[notification autorelease];
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static PushNotifications::Notification nsUserNotificationToJuceNotification (NSUserNotification* n)
|
||||
{
|
||||
PushNotifications::Notification notif;
|
||||
|
||||
notif.title = nsStringToJuce (n.title);
|
||||
notif.subtitle = nsStringToJuce (n.subtitle);
|
||||
notif.body = nsStringToJuce (n.informativeText);
|
||||
|
||||
notif.repeat = n.deliveryRepeatInterval != nil;
|
||||
|
||||
if (n.deliveryRepeatInterval != nil)
|
||||
{
|
||||
notif.triggerIntervalSec = (double) n.deliveryRepeatInterval.second + ((double) n.deliveryRepeatInterval.nanosecond / 1000000000.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
NSDate* dateNow = [NSDate date];
|
||||
NSDate* deliveryDate = n.deliveryDate;
|
||||
|
||||
notif.triggerIntervalSec = [dateNow timeIntervalSinceDate: deliveryDate];
|
||||
}
|
||||
|
||||
notif.soundToPlay = URL (nsStringToJuce (n.soundName));
|
||||
notif.properties = nsDictionaryToVar (n.userInfo);
|
||||
|
||||
notif.identifier = nsStringToJuce (n.identifier);
|
||||
|
||||
if (n.contentImage != nil)
|
||||
notif.icon = nsStringToJuce ([n.contentImage name]);
|
||||
|
||||
Array<Action> actions;
|
||||
|
||||
if (n.actionButtonTitle != nil)
|
||||
{
|
||||
Action action;
|
||||
action.title = nsStringToJuce (n.actionButtonTitle);
|
||||
|
||||
if (n.hasReplyButton)
|
||||
action.style = Action::text;
|
||||
|
||||
if (n.responsePlaceholder != nil)
|
||||
action.textInputPlaceholder = nsStringToJuce (n.responsePlaceholder);
|
||||
|
||||
actions.add (action);
|
||||
}
|
||||
|
||||
if (n.additionalActions != nil)
|
||||
{
|
||||
for (NSUserNotificationAction* a in n.additionalActions)
|
||||
{
|
||||
Action action;
|
||||
action.identifier = nsStringToJuce (a.identifier);
|
||||
action.title = nsStringToJuce (a.title);
|
||||
|
||||
actions.add (action);
|
||||
}
|
||||
}
|
||||
|
||||
return notif;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static var getNotificationPropertiesFromDictionaryVar (const var& dictionaryVar)
|
||||
{
|
||||
auto* dictionaryVarObject = dictionaryVar.getDynamicObject();
|
||||
|
||||
if (dictionaryVarObject == nullptr)
|
||||
return {};
|
||||
|
||||
const auto& properties = dictionaryVarObject->getProperties();
|
||||
|
||||
DynamicObject::Ptr propsVarObject = new DynamicObject();
|
||||
|
||||
for (int i = 0; i < properties.size(); ++i)
|
||||
{
|
||||
auto propertyName = properties.getName (i).toString();
|
||||
|
||||
if (propertyName == "aps")
|
||||
continue;
|
||||
|
||||
propsVarObject->setProperty (propertyName, properties.getValueAt (i));
|
||||
}
|
||||
|
||||
return var (propsVarObject.get());
|
||||
}
|
||||
|
||||
static PushNotifications::Notification nsDictionaryToJuceNotification (NSDictionary* dictionary)
|
||||
{
|
||||
const var dictionaryVar = nsDictionaryToVar (dictionary);
|
||||
|
||||
const var apsVar = dictionaryVar.getProperty ("aps", {});
|
||||
|
||||
if (! apsVar.isObject())
|
||||
return {};
|
||||
|
||||
var alertVar = apsVar.getProperty ("alert", {});
|
||||
|
||||
const var titleVar = alertVar.getProperty ("title", {});
|
||||
const var bodyVar = alertVar.isObject() ? alertVar.getProperty ("body", {}) : alertVar;
|
||||
|
||||
const var categoryVar = apsVar.getProperty ("category", {});
|
||||
const var soundVar = apsVar.getProperty ("sound", {});
|
||||
const var badgeVar = apsVar.getProperty ("badge", {});
|
||||
const var threadIdVar = apsVar.getProperty ("thread-id", {});
|
||||
|
||||
PushNotifications::Notification notification;
|
||||
|
||||
notification.title = titleVar .toString();
|
||||
notification.body = bodyVar .toString();
|
||||
notification.groupId = threadIdVar.toString();
|
||||
notification.category = categoryVar.toString();
|
||||
notification.soundToPlay = URL (soundVar.toString());
|
||||
notification.badgeNumber = (int) badgeVar;
|
||||
notification.properties = getNotificationPropertiesFromDictionaryVar (dictionaryVar);
|
||||
|
||||
return notification;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct PushNotificationsDelegate
|
||||
{
|
||||
PushNotificationsDelegate() : delegate ([getClass().createInstance() init])
|
||||
{
|
||||
Class::setThis (delegate.get(), this);
|
||||
|
||||
id<NSApplicationDelegate> appDelegate = [[NSApplication sharedApplication] delegate];
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
if ([appDelegate respondsToSelector: @selector (setPushNotificationsDelegate:)])
|
||||
[appDelegate performSelector: @selector (setPushNotificationsDelegate:) withObject: delegate.get()];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = delegate.get();
|
||||
}
|
||||
|
||||
virtual ~PushNotificationsDelegate()
|
||||
{
|
||||
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = nil;
|
||||
}
|
||||
|
||||
virtual void registeredForRemoteNotifications (NSData* deviceToken) = 0;
|
||||
|
||||
virtual void failedToRegisterForRemoteNotifications (NSError* error) = 0;
|
||||
|
||||
virtual void didReceiveRemoteNotification (NSDictionary* userInfo) = 0;
|
||||
|
||||
virtual void didDeliverNotification (NSUserNotification* notification) = 0;
|
||||
|
||||
virtual void didActivateNotification (NSUserNotification* notification) = 0;
|
||||
|
||||
virtual bool shouldPresentNotification (NSUserNotification* notification) = 0;
|
||||
|
||||
protected:
|
||||
NSUniquePtr<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>> delegate;
|
||||
|
||||
private:
|
||||
struct Class final : public ObjCClass<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>>
|
||||
{
|
||||
Class() : ObjCClass<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>> ("JucePushNotificationsDelegate_")
|
||||
{
|
||||
addIvar<PushNotificationsDelegate*> ("self");
|
||||
|
||||
addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications);
|
||||
addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications);
|
||||
addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification);
|
||||
addMethod (@selector (userNotificationCenter:didDeliverNotification:), didDeliverNotification);
|
||||
addMethod (@selector (userNotificationCenter:didActivateNotification:), didActivateNotification);
|
||||
addMethod (@selector (userNotificationCenter:shouldPresentNotification:), shouldPresentNotification);
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static PushNotificationsDelegate& getThis (id self) { return *getIvar<PushNotificationsDelegate*> (self, "self"); }
|
||||
static void setThis (id self, PushNotificationsDelegate* d) { object_setInstanceVariable (self, "self", d); }
|
||||
|
||||
//==============================================================================
|
||||
static void registeredForRemoteNotifications (id self, SEL, NSApplication*,
|
||||
NSData* deviceToken) { getThis (self).registeredForRemoteNotifications (deviceToken); }
|
||||
|
||||
static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication*,
|
||||
NSError* error) { getThis (self).failedToRegisterForRemoteNotifications (error); }
|
||||
|
||||
static void didReceiveRemoteNotification (id self, SEL, NSApplication*,
|
||||
NSDictionary* userInfo) { getThis (self).didReceiveRemoteNotification (userInfo); }
|
||||
|
||||
static void didDeliverNotification (id self, SEL, NSUserNotificationCenter*,
|
||||
NSUserNotification* notification) { getThis (self).didDeliverNotification (notification); }
|
||||
|
||||
static void didActivateNotification (id self, SEL, NSUserNotificationCenter*,
|
||||
NSUserNotification* notification) { getThis (self).didActivateNotification (notification); }
|
||||
|
||||
static bool shouldPresentNotification (id self, SEL, NSUserNotificationCenter*,
|
||||
NSUserNotification* notification) { return getThis (self).shouldPresentNotification (notification); }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
static Class& getClass()
|
||||
{
|
||||
static Class c;
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
bool PushNotifications::Notification::isValid() const noexcept { return true; }
|
||||
|
||||
//==============================================================================
|
||||
struct PushNotifications::Impl : private PushNotificationsDelegate
|
||||
{
|
||||
explicit Impl (PushNotifications& p)
|
||||
: owner (p)
|
||||
{
|
||||
}
|
||||
|
||||
void requestPermissionsWithSettings (const PushNotifications::Settings& settingsToUse)
|
||||
{
|
||||
settings = settingsToUse;
|
||||
|
||||
NSRemoteNotificationType types = NSUInteger ((bool) settings.allowBadge);
|
||||
|
||||
types |= (NSUInteger) ((settings.allowSound ? NSRemoteNotificationTypeSound : 0)
|
||||
| (settings.allowAlert ? NSRemoteNotificationTypeAlert : 0));
|
||||
|
||||
[[NSApplication sharedApplication] registerForRemoteNotificationTypes: types];
|
||||
}
|
||||
|
||||
void requestSettingsUsed()
|
||||
{
|
||||
settings.allowBadge = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeBadge;
|
||||
|
||||
settings.allowSound = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeSound;
|
||||
settings.allowAlert = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeAlert;
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); });
|
||||
}
|
||||
|
||||
bool areNotificationsEnabled() const { return true; }
|
||||
|
||||
void sendLocalNotification (const Notification& n)
|
||||
{
|
||||
auto* notification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n);
|
||||
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification: notification];
|
||||
}
|
||||
|
||||
void getDeliveredNotifications() const
|
||||
{
|
||||
Array<PushNotifications::Notification> notifs;
|
||||
|
||||
for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].deliveredNotifications)
|
||||
notifs.add (PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (n));
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifs); });
|
||||
}
|
||||
|
||||
void removeAllDeliveredNotifications()
|
||||
{
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications];
|
||||
}
|
||||
|
||||
void removeDeliveredNotification (const String& identifier)
|
||||
{
|
||||
PushNotifications::Notification n;
|
||||
n.identifier = identifier;
|
||||
|
||||
auto nsNotification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n);
|
||||
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification: nsNotification];
|
||||
}
|
||||
|
||||
void setupChannels ([[maybe_unused]] const Array<ChannelGroup>& groups, [[maybe_unused]] const Array<Channel>& channels)
|
||||
{
|
||||
}
|
||||
|
||||
void getPendingLocalNotifications() const
|
||||
{
|
||||
Array<PushNotifications::Notification> notifs;
|
||||
|
||||
for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].scheduledNotifications)
|
||||
notifs.add (PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (n));
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
|
||||
}
|
||||
|
||||
void removePendingLocalNotification (const String& identifier)
|
||||
{
|
||||
PushNotifications::Notification n;
|
||||
n.identifier = identifier;
|
||||
|
||||
auto nsNotification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n);
|
||||
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] removeScheduledNotification: nsNotification];
|
||||
}
|
||||
|
||||
void removeAllPendingLocalNotifications()
|
||||
{
|
||||
for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].scheduledNotifications)
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] removeScheduledNotification: n];
|
||||
}
|
||||
|
||||
String getDeviceToken()
|
||||
{
|
||||
// You need to call requestPermissionsWithSettings() first.
|
||||
jassert (initialised);
|
||||
|
||||
return deviceToken;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
//PushNotificationsDelegate
|
||||
void registeredForRemoteNotifications (NSData* deviceTokenToUse) override
|
||||
{
|
||||
deviceToken = [deviceTokenToUse]() -> String
|
||||
{
|
||||
auto length = deviceTokenToUse.length;
|
||||
|
||||
if (auto* buffer = (const unsigned char*) deviceTokenToUse.bytes)
|
||||
{
|
||||
NSMutableString* hexString = [NSMutableString stringWithCapacity: (length * 2)];
|
||||
|
||||
for (NSUInteger i = 0; i < length; ++i)
|
||||
[hexString appendFormat:@"%02x", buffer[i]];
|
||||
|
||||
return nsStringToJuce ([hexString copy]);
|
||||
}
|
||||
|
||||
return {};
|
||||
}();
|
||||
|
||||
initialised = true;
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.deviceTokenRefreshed (deviceToken); });
|
||||
}
|
||||
|
||||
void failedToRegisterForRemoteNotifications ([[maybe_unused]] NSError* error) override
|
||||
{
|
||||
deviceToken.clear();
|
||||
}
|
||||
|
||||
void didReceiveRemoteNotification (NSDictionary* userInfo) override
|
||||
{
|
||||
auto n = PushNotificationsDelegateDetailsOsx::nsDictionaryToJuceNotification (userInfo);
|
||||
owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, n); });
|
||||
}
|
||||
|
||||
void didDeliverNotification ([[maybe_unused]] NSUserNotification* notification) override
|
||||
{
|
||||
}
|
||||
|
||||
void didActivateNotification (NSUserNotification* notification) override
|
||||
{
|
||||
auto n = PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (notification);
|
||||
|
||||
if (notification.activationType == NSUserNotificationActivationTypeContentsClicked)
|
||||
{
|
||||
owner.listeners.call ([&] (Listener& l) { l.handleNotification (notification.remote, n); });
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto actionIdentifier = nsStringToJuce ([&]
|
||||
{
|
||||
if (notification.additionalActivationAction != nil)
|
||||
return notification.additionalActivationAction.identifier;
|
||||
|
||||
return notification.actionButtonTitle;
|
||||
}());
|
||||
|
||||
auto reply = notification.activationType == NSUserNotificationActivationTypeReplied
|
||||
? nsStringToJuce ([notification.response string])
|
||||
: String();
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (notification.remote, n, actionIdentifier, reply); });
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldPresentNotification (NSUserNotification*) override { return true; }
|
||||
|
||||
void subscribeToTopic ([[maybe_unused]] const String& topic) {}
|
||||
void unsubscribeFromTopic ([[maybe_unused]] const String& topic) {}
|
||||
|
||||
void sendUpstreamMessage ([[maybe_unused]] const String& serverSenderId,
|
||||
[[maybe_unused]] const String& collapseKey,
|
||||
[[maybe_unused]] const String& messageId,
|
||||
[[maybe_unused]] const String& messageType,
|
||||
[[maybe_unused]] int timeToLive,
|
||||
[[maybe_unused]] const StringPairArray& additionalData)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
PushNotifications& owner;
|
||||
|
||||
bool initialised = false;
|
||||
String deviceToken;
|
||||
|
||||
PushNotifications::Settings settings;
|
||||
};
|
||||
|
||||
JUCE_END_IGNORE_DEPRECATION_WARNINGS
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class SystemTrayIconComponent::Pimpl
|
||||
{
|
||||
public:
|
||||
Pimpl (const Image& im, Window windowH) : image (im)
|
||||
{
|
||||
XWindowSystemUtilities::ScopedXLock xLock;
|
||||
|
||||
auto* display = XWindowSystem::getInstance()->getDisplay();
|
||||
|
||||
auto* screen = X11Symbols::getInstance()->xDefaultScreenOfDisplay (display);
|
||||
auto screenNumber = X11Symbols::getInstance()->xScreenNumberOfScreen (screen);
|
||||
|
||||
String screenAtom ("_NET_SYSTEM_TRAY_S");
|
||||
screenAtom << screenNumber;
|
||||
Atom selectionAtom = XWindowSystemUtilities::Atoms::getCreating (display, screenAtom.toUTF8());
|
||||
|
||||
X11Symbols::getInstance()->xGrabServer (display);
|
||||
auto managerWin = X11Symbols::getInstance()->xGetSelectionOwner (display, selectionAtom);
|
||||
|
||||
if (managerWin != None)
|
||||
X11Symbols::getInstance()->xSelectInput (display, managerWin, StructureNotifyMask);
|
||||
|
||||
X11Symbols::getInstance()->xUngrabServer (display);
|
||||
X11Symbols::getInstance()->xFlush (display);
|
||||
|
||||
if (managerWin != None)
|
||||
{
|
||||
XEvent ev = { 0 };
|
||||
ev.xclient.type = ClientMessage;
|
||||
ev.xclient.window = managerWin;
|
||||
ev.xclient.message_type = XWindowSystemUtilities::Atoms::getCreating (display, "_NET_SYSTEM_TRAY_OPCODE");
|
||||
ev.xclient.format = 32;
|
||||
ev.xclient.data.l[0] = CurrentTime;
|
||||
ev.xclient.data.l[1] = 0 /*SYSTEM_TRAY_REQUEST_DOCK*/;
|
||||
ev.xclient.data.l[2] = (long) windowH;
|
||||
ev.xclient.data.l[3] = 0;
|
||||
ev.xclient.data.l[4] = 0;
|
||||
|
||||
X11Symbols::getInstance()->xSendEvent (display, managerWin, False, NoEventMask, &ev);
|
||||
X11Symbols::getInstance()->xSync (display, False);
|
||||
}
|
||||
|
||||
// For older KDE's ...
|
||||
long atomData = 1;
|
||||
Atom trayAtom = XWindowSystemUtilities::Atoms::getCreating (display, "KWM_DOCKWINDOW");
|
||||
X11Symbols::getInstance()->xChangeProperty (display, windowH, trayAtom, trayAtom,
|
||||
32, PropModeReplace, (unsigned char*) &atomData, 1);
|
||||
|
||||
// For more recent KDE's...
|
||||
trayAtom = XWindowSystemUtilities::Atoms::getCreating (display, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR");
|
||||
X11Symbols::getInstance()->xChangeProperty (display, windowH, trayAtom, XA_WINDOW,
|
||||
32, PropModeReplace, (unsigned char*) &windowH, 1);
|
||||
|
||||
// A minimum size must be specified for GNOME and Xfce, otherwise the icon is displayed with a width of 1
|
||||
if (auto* hints = X11Symbols::getInstance()->xAllocSizeHints())
|
||||
{
|
||||
hints->flags = PMinSize;
|
||||
hints->min_width = 22;
|
||||
hints->min_height = 22;
|
||||
X11Symbols::getInstance()->xSetWMNormalHints (display, windowH, hints);
|
||||
X11Symbols::getInstance()->xFree (hints);
|
||||
}
|
||||
}
|
||||
|
||||
Image image;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
void SystemTrayIconComponent::setIconImage (const Image& colourImage, const Image&)
|
||||
{
|
||||
pimpl.reset();
|
||||
|
||||
if (colourImage.isValid())
|
||||
{
|
||||
if (! isOnDesktop())
|
||||
addToDesktop (0);
|
||||
|
||||
pimpl.reset (new Pimpl (colourImage, (Window) getWindowHandle()));
|
||||
|
||||
setVisible (true);
|
||||
toFront (false);
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::paint (Graphics& g)
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
g.drawImage (pimpl->image, getLocalBounds().toFloat(),
|
||||
RectanglePlacement::xLeft | RectanglePlacement::yTop | RectanglePlacement::onlyReduceInSize);
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::setIconTooltip (const String& /*tooltip*/)
|
||||
{
|
||||
// xxx Not implemented!
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::setHighlighted (bool)
|
||||
{
|
||||
// xxx Not implemented!
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/)
|
||||
{
|
||||
// xxx Not implemented!
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::hideInfoBubble()
|
||||
{
|
||||
// xxx Not implemented!
|
||||
}
|
||||
|
||||
void* SystemTrayIconComponent::getNativeHandle() const
|
||||
{
|
||||
return getWindowHandle();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,451 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
JUCE_BEGIN_IGNORE_DEPRECATION_WARNINGS
|
||||
|
||||
extern NSMenu* createNSMenu (const PopupMenu&, const String& name, int topLevelMenuId,
|
||||
int topLevelIndex, bool addDelegate);
|
||||
|
||||
//==============================================================================
|
||||
struct StatusItemContainer : public Timer
|
||||
{
|
||||
//==============================================================================
|
||||
StatusItemContainer (SystemTrayIconComponent& iconComp, const Image& im)
|
||||
: owner (iconComp), statusIcon (imageToNSImage (ScaledImage (im)))
|
||||
{
|
||||
}
|
||||
|
||||
virtual void configureIcon() = 0;
|
||||
virtual void setHighlighted (bool shouldHighlight) = 0;
|
||||
|
||||
//==============================================================================
|
||||
void setIconSize()
|
||||
{
|
||||
[statusIcon.get() setSize: NSMakeSize (20.0f, 20.0f)];
|
||||
}
|
||||
|
||||
void updateIcon (const Image& newImage)
|
||||
{
|
||||
statusIcon.reset (imageToNSImage (ScaledImage (newImage)));
|
||||
setIconSize();
|
||||
configureIcon();
|
||||
}
|
||||
|
||||
void showMenu (const PopupMenu& menu)
|
||||
{
|
||||
if (NSMenu* m = createNSMenu (menu, "MenuBarItem", -2, -3, true))
|
||||
{
|
||||
setHighlighted (true);
|
||||
stopTimer();
|
||||
|
||||
// There's currently no good alternative to this.
|
||||
[statusItem.get() popUpStatusItemMenu: m];
|
||||
|
||||
startTimer (1);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void timerCallback() override
|
||||
{
|
||||
stopTimer();
|
||||
setHighlighted (false);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
SystemTrayIconComponent& owner;
|
||||
|
||||
NSUniquePtr<NSStatusItem> statusItem;
|
||||
NSUniquePtr<NSImage> statusIcon;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StatusItemContainer)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct ButtonBasedStatusItem final : public StatusItemContainer
|
||||
{
|
||||
//==============================================================================
|
||||
ButtonBasedStatusItem (SystemTrayIconComponent& iconComp, const Image& im)
|
||||
: StatusItemContainer (iconComp, im)
|
||||
{
|
||||
static ButtonEventForwarderClass cls;
|
||||
eventForwarder.reset ([cls.createInstance() init]);
|
||||
ButtonEventForwarderClass::setOwner (eventForwarder.get(), this);
|
||||
|
||||
setIconSize();
|
||||
configureIcon();
|
||||
|
||||
statusItem.reset ([[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]);
|
||||
auto button = [statusItem.get() button];
|
||||
button.image = statusIcon.get();
|
||||
button.target = eventForwarder.get();
|
||||
button.action = @selector (handleEvent:);
|
||||
[button sendActionOn: NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskScrollWheel];
|
||||
}
|
||||
|
||||
~ButtonBasedStatusItem() override
|
||||
{
|
||||
[statusItem.get() button].image = nullptr;
|
||||
}
|
||||
|
||||
void configureIcon() override
|
||||
{
|
||||
[statusIcon.get() setTemplate: true];
|
||||
[statusItem.get() button].image = statusIcon.get();
|
||||
}
|
||||
|
||||
void setHighlighted (bool shouldHighlight) override
|
||||
{
|
||||
[[statusItem.get() button] setHighlighted: shouldHighlight];
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void handleEvent()
|
||||
{
|
||||
auto e = [NSApp currentEvent];
|
||||
NSEventType type = [e type];
|
||||
|
||||
const bool isLeft = (type == NSEventTypeLeftMouseDown);
|
||||
const bool isRight = (type == NSEventTypeRightMouseDown);
|
||||
|
||||
if (owner.isCurrentlyBlockedByAnotherModalComponent())
|
||||
{
|
||||
if (isLeft || isRight)
|
||||
if (auto* current = Component::getCurrentlyModalComponent())
|
||||
current->inputAttemptWhenModal();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto eventMods = ComponentPeer::getCurrentModifiersRealtime();
|
||||
|
||||
if (([e modifierFlags] & NSEventModifierFlagCommand) != 0)
|
||||
eventMods = eventMods.withFlags (ModifierKeys::commandModifier);
|
||||
|
||||
auto now = Time::getCurrentTime();
|
||||
auto mouseSource = Desktop::getInstance().getMainMouseSource();
|
||||
auto pressure = (float) e.pressure;
|
||||
|
||||
if (isLeft || isRight)
|
||||
{
|
||||
owner.mouseDown ({ mouseSource, {},
|
||||
eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier
|
||||
: ModifierKeys::rightButtonModifier),
|
||||
pressure,
|
||||
MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
|
||||
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
|
||||
&owner, &owner, now, {}, now, 1, false });
|
||||
|
||||
owner.mouseUp ({ mouseSource, {},
|
||||
eventMods.withoutMouseButtons(),
|
||||
pressure,
|
||||
MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
|
||||
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
|
||||
&owner, &owner, now, {}, now, 1, false });
|
||||
}
|
||||
else if (type == NSEventTypeMouseMoved)
|
||||
{
|
||||
owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure,
|
||||
MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
|
||||
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
|
||||
&owner, &owner, now, {}, now, 1, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class ButtonEventForwarderClass final : public ObjCClass<NSObject>
|
||||
{
|
||||
public:
|
||||
ButtonEventForwarderClass() : ObjCClass<NSObject> ("JUCEButtonEventForwarderClass_")
|
||||
{
|
||||
addIvar<ButtonBasedStatusItem*> ("owner");
|
||||
|
||||
addMethod (@selector (handleEvent:), handleEvent);
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static ButtonBasedStatusItem* getOwner (id self) { return getIvar<ButtonBasedStatusItem*> (self, "owner"); }
|
||||
static void setOwner (id self, ButtonBasedStatusItem* owner) { object_setInstanceVariable (self, "owner", owner); }
|
||||
|
||||
private:
|
||||
static void handleEvent (id self, SEL, id)
|
||||
{
|
||||
if (auto* owner = getOwner (self))
|
||||
owner->handleEvent();
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
NSUniquePtr<NSObject> eventForwarder;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct ViewBasedStatusItem final : public StatusItemContainer
|
||||
{
|
||||
//==============================================================================
|
||||
ViewBasedStatusItem (SystemTrayIconComponent& iconComp, const Image& im)
|
||||
: StatusItemContainer (iconComp, im)
|
||||
{
|
||||
static SystemTrayViewClass cls;
|
||||
view.reset ([cls.createInstance() init]);
|
||||
SystemTrayViewClass::setOwner (view.get(), this);
|
||||
SystemTrayViewClass::setImage (view.get(), statusIcon.get());
|
||||
|
||||
setIconSize();
|
||||
|
||||
statusItem.reset ([[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]);
|
||||
[statusItem.get() setView: view.get()];
|
||||
|
||||
SystemTrayViewClass::frameChanged (view.get(), SEL(), nullptr);
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
[[NSNotificationCenter defaultCenter] addObserver: view.get()
|
||||
selector: @selector (frameChanged:)
|
||||
name: NSWindowDidMoveNotification
|
||||
object: nil];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
~ViewBasedStatusItem() override
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver: view.get()];
|
||||
[[NSStatusBar systemStatusBar] removeStatusItem: statusItem.get()];
|
||||
SystemTrayViewClass::setOwner (view.get(), nullptr);
|
||||
SystemTrayViewClass::setImage (view.get(), nil);
|
||||
}
|
||||
|
||||
void configureIcon() override
|
||||
{
|
||||
SystemTrayViewClass::setImage (view.get(), statusIcon.get());
|
||||
[statusItem.get() setView: view.get()];
|
||||
}
|
||||
|
||||
void setHighlighted (bool shouldHighlight) override
|
||||
{
|
||||
isHighlighted = shouldHighlight;
|
||||
[view.get() setNeedsDisplay: true];
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void handleStatusItemAction (NSEvent* e)
|
||||
{
|
||||
NSEventType type = [e type];
|
||||
|
||||
const bool isLeft = (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp);
|
||||
const bool isRight = (type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp);
|
||||
|
||||
if (owner.isCurrentlyBlockedByAnotherModalComponent())
|
||||
{
|
||||
if (isLeft || isRight)
|
||||
if (auto* current = Component::getCurrentlyModalComponent())
|
||||
current->inputAttemptWhenModal();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto eventMods = ComponentPeer::getCurrentModifiersRealtime();
|
||||
|
||||
if (([e modifierFlags] & NSEventModifierFlagCommand) != 0)
|
||||
eventMods = eventMods.withFlags (ModifierKeys::commandModifier);
|
||||
|
||||
auto now = Time::getCurrentTime();
|
||||
auto mouseSource = Desktop::getInstance().getMainMouseSource();
|
||||
auto pressure = (float) e.pressure;
|
||||
|
||||
if (isLeft || isRight) // Only mouse up is sent by the OS, so simulate a down/up
|
||||
{
|
||||
setHighlighted (true);
|
||||
startTimer (150);
|
||||
|
||||
owner.mouseDown (MouseEvent (mouseSource, {},
|
||||
eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier
|
||||
: ModifierKeys::rightButtonModifier),
|
||||
pressure, MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
|
||||
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
|
||||
&owner, &owner, now, {}, now, 1, false));
|
||||
|
||||
owner.mouseUp (MouseEvent (mouseSource, {}, eventMods.withoutMouseButtons(), pressure,
|
||||
MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
|
||||
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
|
||||
&owner, &owner, now, {}, now, 1, false));
|
||||
}
|
||||
else if (type == NSEventTypeMouseMoved)
|
||||
{
|
||||
owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure,
|
||||
MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
|
||||
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
|
||||
&owner, &owner, now, {}, now, 1, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct SystemTrayViewClass final : public ObjCClass<NSControl>
|
||||
{
|
||||
SystemTrayViewClass() : ObjCClass<NSControl> ("JUCESystemTrayView_")
|
||||
{
|
||||
addIvar<ViewBasedStatusItem*> ("owner");
|
||||
addIvar<NSImage*> ("image");
|
||||
|
||||
addMethod (@selector (mouseDown:), handleEventDown);
|
||||
addMethod (@selector (rightMouseDown:), handleEventDown);
|
||||
addMethod (@selector (drawRect:), drawRect);
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
addMethod (@selector (frameChanged:), frameChanged);
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static ViewBasedStatusItem* getOwner (id self) { return getIvar<ViewBasedStatusItem*> (self, "owner"); }
|
||||
static NSImage* getImage (id self) { return getIvar<NSImage*> (self, "image"); }
|
||||
static void setOwner (id self, ViewBasedStatusItem* owner) { object_setInstanceVariable (self, "owner", owner); }
|
||||
static void setImage (id self, NSImage* image) { object_setInstanceVariable (self, "image", image); }
|
||||
|
||||
static void frameChanged (id self, SEL, NSNotification*)
|
||||
{
|
||||
if (auto* owner = getOwner (self))
|
||||
{
|
||||
NSRect r = [[[owner->statusItem.get() view] window] frame];
|
||||
NSRect sr = [[[NSScreen screens] objectAtIndex: 0] frame];
|
||||
r.origin.y = sr.size.height - r.origin.y - r.size.height;
|
||||
owner->owner.setBounds (convertToRectInt (r));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static void handleEventDown (id self, SEL, NSEvent* e)
|
||||
{
|
||||
if (auto* owner = getOwner (self))
|
||||
owner->handleStatusItemAction (e);
|
||||
}
|
||||
|
||||
static void drawRect (id self, SEL, NSRect)
|
||||
{
|
||||
NSRect bounds = [self bounds];
|
||||
|
||||
if (auto* owner = getOwner (self))
|
||||
[owner->statusItem.get() drawStatusBarBackgroundInRect: bounds
|
||||
withHighlight: owner->isHighlighted];
|
||||
|
||||
if (NSImage* const im = getImage (self))
|
||||
{
|
||||
NSSize imageSize = [im size];
|
||||
|
||||
[im drawInRect: NSMakeRect (bounds.origin.x + ((bounds.size.width - imageSize.width) / 2.0f),
|
||||
bounds.origin.y + ((bounds.size.height - imageSize.height) / 2.0f),
|
||||
imageSize.width, imageSize.height)
|
||||
fromRect: NSZeroRect
|
||||
operation: NSCompositingOperationSourceOver
|
||||
fraction: 1.0f];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
NSUniquePtr<NSControl> view;
|
||||
bool isHighlighted = false;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class SystemTrayIconComponent::Pimpl
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
Pimpl (SystemTrayIconComponent& iconComp, const Image& im)
|
||||
: statusItemHolder (std::make_unique<ButtonBasedStatusItem> (iconComp, im))
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<StatusItemContainer> statusItemHolder;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
void SystemTrayIconComponent::setIconImage (const Image&, const Image& templateImage)
|
||||
{
|
||||
if (templateImage.isValid())
|
||||
{
|
||||
if (pimpl == nullptr)
|
||||
pimpl.reset (new Pimpl (*this, templateImage));
|
||||
else
|
||||
pimpl->statusItemHolder->updateIcon (templateImage);
|
||||
}
|
||||
else
|
||||
{
|
||||
pimpl.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::setIconTooltip (const String&)
|
||||
{
|
||||
// xxx not yet implemented!
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::setHighlighted (bool shouldHighlight)
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
pimpl->statusItemHolder->setHighlighted (shouldHighlight);
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/)
|
||||
{
|
||||
// xxx Not implemented!
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::hideInfoBubble()
|
||||
{
|
||||
// xxx Not implemented!
|
||||
}
|
||||
|
||||
void* SystemTrayIconComponent::getNativeHandle() const
|
||||
{
|
||||
return pimpl != nullptr ? pimpl->statusItemHolder->statusItem.get() : nullptr;
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::showDropdownMenu (const PopupMenu& menu)
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
pimpl->statusItemHolder->showMenu (menu);
|
||||
}
|
||||
|
||||
JUCE_END_IGNORE_DEPRECATION_WARNINGS
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
extern void* getUser32Function (const char*);
|
||||
|
||||
namespace IconConverters
|
||||
{
|
||||
extern HICON createHICONFromImage (const Image&, BOOL isIcon, int hotspotX, int hotspotY);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class SystemTrayIconComponent::Pimpl
|
||||
{
|
||||
public:
|
||||
Pimpl (SystemTrayIconComponent& owner_, HICON hicon, HWND hwnd)
|
||||
: owner (owner_),
|
||||
originalWndProc ((WNDPROC) GetWindowLongPtr (hwnd, GWLP_WNDPROC)),
|
||||
taskbarCreatedMessage (RegisterWindowMessage (TEXT ("TaskbarCreated")))
|
||||
{
|
||||
SetWindowLongPtr (hwnd, GWLP_WNDPROC, (LONG_PTR) hookedWndProc);
|
||||
|
||||
zerostruct (iconData);
|
||||
iconData.cbSize = sizeof (iconData);
|
||||
iconData.hWnd = hwnd;
|
||||
iconData.uID = (UINT) (pointer_sized_int) hwnd;
|
||||
iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
|
||||
iconData.uCallbackMessage = WM_TRAYNOTIFY;
|
||||
iconData.hIcon = hicon;
|
||||
|
||||
notify (NIM_ADD);
|
||||
|
||||
// In order to receive the "TaskbarCreated" message, we need to request that it's not filtered out.
|
||||
// (Need to load dynamically, as ChangeWindowMessageFilter is only available in Vista and later)
|
||||
typedef BOOL (WINAPI* ChangeWindowMessageFilterType) (UINT, DWORD);
|
||||
|
||||
if (ChangeWindowMessageFilterType changeWindowMessageFilter
|
||||
= (ChangeWindowMessageFilterType) getUser32Function ("ChangeWindowMessageFilter"))
|
||||
changeWindowMessageFilter (taskbarCreatedMessage, 1 /* MSGFLT_ADD */);
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
SetWindowLongPtr (iconData.hWnd, GWLP_WNDPROC, (LONG_PTR) originalWndProc);
|
||||
|
||||
iconData.uFlags = 0;
|
||||
notify (NIM_DELETE);
|
||||
DestroyIcon (iconData.hIcon);
|
||||
}
|
||||
|
||||
void updateIcon (HICON hicon)
|
||||
{
|
||||
HICON oldIcon = iconData.hIcon;
|
||||
|
||||
iconData.hIcon = hicon;
|
||||
iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
|
||||
notify (NIM_MODIFY);
|
||||
|
||||
DestroyIcon (oldIcon);
|
||||
}
|
||||
|
||||
void setToolTip (const String& toolTip)
|
||||
{
|
||||
iconData.uFlags = NIF_TIP;
|
||||
toolTip.copyToUTF16 (iconData.szTip, sizeof (iconData.szTip) - 1);
|
||||
notify (NIM_MODIFY);
|
||||
}
|
||||
|
||||
void handleTaskBarEvent (const LPARAM lParam)
|
||||
{
|
||||
if (owner.isCurrentlyBlockedByAnotherModalComponent())
|
||||
{
|
||||
if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN
|
||||
|| lParam == WM_LBUTTONDBLCLK || lParam == WM_RBUTTONDBLCLK)
|
||||
{
|
||||
if (auto* current = Component::getCurrentlyModalComponent())
|
||||
current->inputAttemptWhenModal();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ModifierKeys eventMods (ComponentPeer::getCurrentModifiersRealtime());
|
||||
|
||||
if (lParam == WM_LBUTTONDOWN || lParam == WM_LBUTTONDBLCLK)
|
||||
eventMods = eventMods.withFlags (ModifierKeys::leftButtonModifier);
|
||||
else if (lParam == WM_RBUTTONDOWN || lParam == WM_RBUTTONDBLCLK)
|
||||
eventMods = eventMods.withFlags (ModifierKeys::rightButtonModifier);
|
||||
else if (lParam == WM_LBUTTONUP || lParam == WM_RBUTTONUP)
|
||||
eventMods = eventMods.withoutMouseButtons();
|
||||
|
||||
const Time eventTime (getMouseEventTime());
|
||||
|
||||
const MouseEvent e (Desktop::getInstance().getMainMouseSource(), {}, eventMods,
|
||||
MouseInputSource::defaultPressure, MouseInputSource::defaultOrientation,
|
||||
MouseInputSource::defaultRotation, MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
|
||||
&owner, &owner, eventTime, {}, eventTime, 1, false);
|
||||
|
||||
if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN)
|
||||
{
|
||||
SetFocus (iconData.hWnd);
|
||||
SetForegroundWindow (iconData.hWnd);
|
||||
owner.mouseDown (e);
|
||||
}
|
||||
else if (lParam == WM_LBUTTONUP || lParam == WM_RBUTTONUP)
|
||||
{
|
||||
owner.mouseUp (e);
|
||||
}
|
||||
else if (lParam == WM_LBUTTONDBLCLK || lParam == WM_RBUTTONDBLCLK)
|
||||
{
|
||||
owner.mouseDoubleClick (e);
|
||||
}
|
||||
else if (lParam == WM_MOUSEMOVE)
|
||||
{
|
||||
owner.mouseMove (e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Pimpl* getPimpl (HWND hwnd)
|
||||
{
|
||||
if (JuceWindowIdentifier::isJUCEWindow (hwnd))
|
||||
if (ComponentPeer* peer = (ComponentPeer*) GetWindowLongPtr (hwnd, 8))
|
||||
if (SystemTrayIconComponent* const iconComp = dynamic_cast<SystemTrayIconComponent*> (&(peer->getComponent())))
|
||||
return iconComp->pimpl.get();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK hookedWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (Pimpl* const p = getPimpl (hwnd))
|
||||
return p->windowProc (hwnd, message, wParam, lParam);
|
||||
|
||||
return DefWindowProcW (hwnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
LRESULT windowProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (message == WM_TRAYNOTIFY)
|
||||
{
|
||||
handleTaskBarEvent (lParam);
|
||||
}
|
||||
else if (message == taskbarCreatedMessage)
|
||||
{
|
||||
iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
|
||||
notify (NIM_ADD);
|
||||
}
|
||||
|
||||
return CallWindowProc (originalWndProc, hwnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
void showBubble (const String& title, const String& content)
|
||||
{
|
||||
iconData.uFlags = 0x10 /*NIF_INFO*/;
|
||||
title.copyToUTF16 (iconData.szInfoTitle, sizeof (iconData.szInfoTitle) - 1);
|
||||
content.copyToUTF16 (iconData.szInfo, sizeof (iconData.szInfo) - 1);
|
||||
notify (NIM_MODIFY);
|
||||
}
|
||||
|
||||
SystemTrayIconComponent& owner;
|
||||
NOTIFYICONDATA iconData;
|
||||
|
||||
private:
|
||||
WNDPROC originalWndProc;
|
||||
const DWORD taskbarCreatedMessage;
|
||||
enum { WM_TRAYNOTIFY = WM_USER + 100 };
|
||||
|
||||
void notify (DWORD message) noexcept { Shell_NotifyIcon (message, &iconData); }
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
void SystemTrayIconComponent::setIconImage (const Image& colourImage, const Image&)
|
||||
{
|
||||
if (colourImage.isValid())
|
||||
{
|
||||
HICON hicon = IconConverters::createHICONFromImage (colourImage, TRUE, 0, 0);
|
||||
|
||||
if (pimpl == nullptr)
|
||||
pimpl.reset (new Pimpl (*this, hicon, (HWND) getWindowHandle()));
|
||||
else
|
||||
pimpl->updateIcon (hicon);
|
||||
}
|
||||
else
|
||||
{
|
||||
pimpl.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::setIconTooltip (const String& tooltip)
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
pimpl->setToolTip (tooltip);
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::setHighlighted (bool)
|
||||
{
|
||||
// N/A on Windows.
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::showInfoBubble (const String& title, const String& content)
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
pimpl->showBubble (title, content);
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::hideInfoBubble()
|
||||
{
|
||||
showInfoBubble (String(), String());
|
||||
}
|
||||
|
||||
void* SystemTrayIconComponent::getNativeHandle() const
|
||||
{
|
||||
return pimpl != nullptr ? &(pimpl->iconData) : nullptr;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class UIViewComponent::Pimpl : public ComponentMovementWatcher
|
||||
{
|
||||
public:
|
||||
Pimpl (UIView* v, Component& comp)
|
||||
: ComponentMovementWatcher (&comp),
|
||||
view (v),
|
||||
owner (comp)
|
||||
{
|
||||
[view retain];
|
||||
|
||||
if (owner.isShowing())
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
~Pimpl() override
|
||||
{
|
||||
[view removeFromSuperview];
|
||||
[view release];
|
||||
}
|
||||
|
||||
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
|
||||
{
|
||||
auto* topComp = owner.getTopLevelComponent();
|
||||
|
||||
if (topComp->getPeer() != nullptr)
|
||||
{
|
||||
auto pos = topComp->getLocalPoint (&owner, Point<int>());
|
||||
|
||||
[view setFrame: CGRectMake ((float) pos.x, (float) pos.y,
|
||||
(float) owner.getWidth(), (float) owner.getHeight())];
|
||||
}
|
||||
}
|
||||
|
||||
void componentPeerChanged() override
|
||||
{
|
||||
auto* peer = owner.getPeer();
|
||||
|
||||
if (currentPeer != peer)
|
||||
{
|
||||
if ([view superview] != nil)
|
||||
[view removeFromSuperview]; // Must be careful not to call this unless it's required - e.g. some Apple AU views
|
||||
// override the call and use it as a sign that they're being deleted, which breaks everything..
|
||||
currentPeer = peer;
|
||||
|
||||
if (peer != nullptr)
|
||||
{
|
||||
UIView* peerView = (UIView*) peer->getNativeHandle();
|
||||
[peerView addSubview: view];
|
||||
componentMovedOrResized (false, false);
|
||||
}
|
||||
}
|
||||
|
||||
[view setHidden: ! owner.isShowing()];
|
||||
}
|
||||
|
||||
void componentVisibilityChanged() override
|
||||
{
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
Rectangle<int> getViewBounds() const
|
||||
{
|
||||
CGRect r = [view frame];
|
||||
return Rectangle<int> ((int) r.size.width, (int) r.size.height);
|
||||
}
|
||||
|
||||
UIView* const view;
|
||||
|
||||
private:
|
||||
Component& owner;
|
||||
ComponentPeer* currentPeer = nullptr;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
UIViewComponent::UIViewComponent() {}
|
||||
UIViewComponent::~UIViewComponent()
|
||||
{
|
||||
AccessibilityHandler::setNativeChildForComponent (*this, nullptr);
|
||||
}
|
||||
|
||||
void UIViewComponent::setView (void* view)
|
||||
{
|
||||
if (view != getView())
|
||||
{
|
||||
pimpl.reset();
|
||||
|
||||
if (view != nullptr)
|
||||
pimpl.reset (new Pimpl ((UIView*) view, *this));
|
||||
|
||||
AccessibilityHandler::setNativeChildForComponent (*this, getView());
|
||||
}
|
||||
}
|
||||
|
||||
void* UIViewComponent::getView() const
|
||||
{
|
||||
return pimpl == nullptr ? nullptr : pimpl->view;
|
||||
}
|
||||
|
||||
void UIViewComponent::resizeToFitView()
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
setBounds (pimpl->getViewBounds());
|
||||
}
|
||||
|
||||
void UIViewComponent::paint (Graphics&) {}
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> UIViewComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
+953
@@ -0,0 +1,953 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
// This byte-code is generated from native/java/app/com/rmsl/juce/JuceWebViewClasses.java with min sdk version 23.
|
||||
// See juce_core/native/java/README.txt on how to generate this byte-code.
|
||||
static const uint8 juceWebViewClasses[] =
|
||||
{
|
||||
0x1f, 0x8b, 0x08, 0x08, 0x96, 0xd6, 0x11, 0x65, 0x00, 0x03, 0x63, 0x6c,
|
||||
0x61, 0x73, 0x73, 0x65, 0x73, 0x2e, 0x64, 0x65, 0x78, 0x00, 0x95, 0x59,
|
||||
0x6b, 0x6c, 0x1c, 0x57, 0x15, 0x3e, 0x33, 0xb3, 0x0f, 0xbf, 0xf6, 0x6d,
|
||||
0x7b, 0xd7, 0x8f, 0x38, 0x9b, 0xb5, 0xdb, 0x3c, 0x1a, 0x67, 0x6d, 0x27,
|
||||
0x4e, 0xec, 0x38, 0xa4, 0x8e, 0x1d, 0x87, 0xc4, 0xac, 0xdb, 0x26, 0xb6,
|
||||
0x37, 0xd4, 0x09, 0x4d, 0xc7, 0xbb, 0x13, 0xef, 0x26, 0xeb, 0x99, 0x65,
|
||||
0x67, 0xd6, 0x09, 0x54, 0x04, 0x53, 0x82, 0x14, 0xa9, 0x0f, 0x55, 0xe2,
|
||||
0x55, 0x21, 0x28, 0x41, 0x0a, 0x52, 0xa9, 0x5a, 0x29, 0x3f, 0x4a, 0x55,
|
||||
0x5a, 0x7e, 0x14, 0xd4, 0x1f, 0x15, 0x14, 0x10, 0x10, 0xa9, 0x95, 0xa8,
|
||||
0xa0, 0x88, 0xa2, 0x46, 0xa8, 0x48, 0xfc, 0x00, 0x29, 0x42, 0x88, 0xf2,
|
||||
0xdd, 0x3b, 0x77, 0xd6, 0xe3, 0x8d, 0x4b, 0xda, 0xb5, 0xbf, 0x3d, 0xe7,
|
||||
0x9e, 0x73, 0xee, 0xbd, 0xe7, 0x9e, 0x7b, 0xee, 0x99, 0x99, 0x9d, 0xbc,
|
||||
0x76, 0xb1, 0x69, 0x60, 0xf7, 0x30, 0xfd, 0xbc, 0xfa, 0xb5, 0x0f, 0x7f,
|
||||
0x7c, 0xf3, 0xbf, 0x7f, 0x3c, 0x39, 0xf6, 0x98, 0xfc, 0xbd, 0x6b, 0x37,
|
||||
0xae, 0xdf, 0xd4, 0x27, 0x2e, 0x3e, 0xba, 0x2d, 0x9e, 0x6b, 0xe9, 0x24,
|
||||
0x2a, 0x13, 0xd1, 0xc5, 0xec, 0x9e, 0x28, 0x89, 0xcf, 0x5c, 0x07, 0x91,
|
||||
0x41, 0xb6, 0x7c, 0x33, 0xb0, 0x53, 0x26, 0xba, 0x1b, 0xf4, 0x59, 0xd0,
|
||||
0x66, 0x26, 0xf7, 0x10, 0x8d, 0x81, 0xbe, 0x01, 0xea, 0x07, 0xbd, 0xea,
|
||||
0x23, 0x7a, 0x1c, 0xbd, 0x57, 0xd1, 0x18, 0x08, 0x12, 0x0d, 0x01, 0xfb,
|
||||
0x80, 0x7b, 0x81, 0x49, 0xe0, 0x38, 0xf0, 0x20, 0x70, 0x1a, 0x78, 0x18,
|
||||
0x58, 0x06, 0xca, 0x40, 0x15, 0x78, 0x04, 0xf8, 0x32, 0x70, 0x19, 0xf8,
|
||||
0x3e, 0xf0, 0x0a, 0xf0, 0x2b, 0xe0, 0x6f, 0x00, 0x85, 0x30, 0x2f, 0x70,
|
||||
0x14, 0x28, 0x00, 0x57, 0x80, 0xe7, 0x80, 0xdf, 0x00, 0xff, 0x02, 0x62,
|
||||
0x61, 0xa2, 0x7e, 0x60, 0x06, 0xb8, 0x04, 0xfc, 0x08, 0xf8, 0x03, 0xd0,
|
||||
0x1c, 0xc1, 0xdc, 0x80, 0x09, 0x7c, 0x1b, 0x78, 0x15, 0x78, 0x17, 0x20,
|
||||
0xf8, 0xd8, 0x0b, 0x4c, 0x00, 0x59, 0xe0, 0x1c, 0xf0, 0x08, 0x70, 0x09,
|
||||
0x78, 0x14, 0xb8, 0x02, 0x3c, 0x01, 0x7c, 0x1d, 0xf8, 0x0e, 0xf0, 0x0c,
|
||||
0xf0, 0x43, 0xe0, 0x39, 0xe0, 0x3a, 0xf0, 0x12, 0xf0, 0x0a, 0xf0, 0x4b,
|
||||
0xe0, 0xd7, 0xc0, 0xef, 0x81, 0xb7, 0x80, 0x77, 0x80, 0xbf, 0x00, 0x7f,
|
||||
0x07, 0xfe, 0x0d, 0x44, 0x63, 0x44, 0x7d, 0xc0, 0x08, 0x70, 0x04, 0x38,
|
||||
0x03, 0xac, 0x00, 0x5f, 0x05, 0x1e, 0x03, 0x5e, 0x00, 0x5e, 0x07, 0x7e,
|
||||
0x01, 0xbc, 0x05, 0xfc, 0x19, 0x78, 0x1f, 0xb8, 0x05, 0xf8, 0x5b, 0x89,
|
||||
0xe2, 0xc0, 0x56, 0x60, 0x2f, 0x70, 0x04, 0x38, 0x05, 0x18, 0xc0, 0x2a,
|
||||
0xf0, 0x04, 0xf0, 0x03, 0xe0, 0x27, 0xc0, 0x6f, 0x81, 0xf7, 0x01, 0xb9,
|
||||
0x8d, 0xa8, 0x1d, 0xd8, 0x01, 0x4c, 0x03, 0xa7, 0x01, 0x0b, 0x78, 0x12,
|
||||
0xf8, 0x16, 0x70, 0x15, 0x78, 0x01, 0x78, 0x13, 0xf8, 0x2b, 0xf0, 0x01,
|
||||
0xe0, 0x6f, 0x47, 0x2c, 0x80, 0x03, 0x40, 0x16, 0x30, 0x00, 0x6c, 0x23,
|
||||
0x89, 0xb0, 0x13, 0xc2, 0x48, 0x22, 0x64, 0x04, 0xb7, 0x08, 0xd3, 0x10,
|
||||
0xba, 0x51, 0x1c, 0x48, 0x00, 0x48, 0x11, 0x42, 0xea, 0x50, 0x17, 0xd0,
|
||||
0x0d, 0x6c, 0x02, 0x7a, 0x44, 0xbe, 0x24, 0x81, 0x2d, 0x40, 0x0a, 0xe8,
|
||||
0x05, 0xfa, 0x80, 0xbb, 0xc8, 0xce, 0xa1, 0xad, 0xc0, 0x36, 0x60, 0x3b,
|
||||
0xb0, 0x83, 0xe5, 0x16, 0x30, 0x4a, 0x76, 0x1e, 0xb1, 0xcf, 0x48, 0x80,
|
||||
0xa8, 0x11, 0x54, 0x11, 0xed, 0x66, 0x81, 0x71, 0xc8, 0x03, 0x82, 0xcf,
|
||||
0x08, 0x9b, 0x16, 0x61, 0xd3, 0x28, 0x7c, 0x60, 0x9f, 0x26, 0xc1, 0xcf,
|
||||
0x05, 0x6c, 0x7d, 0xb7, 0x18, 0x73, 0xa7, 0xf0, 0x91, 0x7d, 0xfa, 0x05,
|
||||
0x7f, 0x1a, 0xf2, 0x5d, 0x82, 0xcf, 0x83, 0x4f, 0x0b, 0xbe, 0x0c, 0x7e,
|
||||
0x50, 0xf0, 0x17, 0x5d, 0xfc, 0x65, 0xf0, 0x03, 0x82, 0x1f, 0x77, 0xd9,
|
||||
0xb3, 0xb9, 0x86, 0x04, 0xff, 0x14, 0xf8, 0xdd, 0x82, 0x7f, 0xda, 0x25,
|
||||
0xbf, 0xe6, 0xe2, 0x9f, 0x07, 0xbf, 0x57, 0xf0, 0x2f, 0xba, 0xf8, 0xd7,
|
||||
0xc0, 0xef, 0x11, 0xfc, 0x9b, 0x2e, 0xf9, 0x0d, 0x57, 0xdf, 0x77, 0xc1,
|
||||
0x0f, 0x0b, 0xfe, 0xa6, 0x4b, 0xfe, 0x4f, 0x57, 0xdf, 0xff, 0xb8, 0xe4,
|
||||
0x9e, 0xe0, 0x9a, 0xbc, 0x25, 0xb8, 0x26, 0x1f, 0x71, 0xd9, 0xb7, 0xb2,
|
||||
0xf3, 0x2a, 0xf8, 0x6e, 0xf0, 0xfb, 0xc5, 0x5e, 0xb2, 0x75, 0x1d, 0x10,
|
||||
0x7c, 0xc6, 0xc5, 0x33, 0xdf, 0x3e, 0x25, 0xf8, 0xbe, 0xa0, 0x5d, 0x0b,
|
||||
0x7a, 0xc4, 0x98, 0x21, 0x64, 0xcb, 0x7d, 0x3c, 0x7f, 0x6c, 0x1a, 0x41,
|
||||
0x06, 0xe5, 0x38, 0x8d, 0xd1, 0x59, 0x91, 0x53, 0x4b, 0x22, 0xaf, 0x26,
|
||||
0x39, 0xb5, 0xed, 0x58, 0x8e, 0x4d, 0x73, 0xda, 0x45, 0x19, 0xe6, 0x93,
|
||||
0x90, 0xb7, 0x62, 0x07, 0x35, 0x9e, 0x0f, 0x5e, 0xca, 0xf3, 0xbd, 0x6e,
|
||||
0x20, 0x99, 0xef, 0xb3, 0x44, 0x9f, 0xe6, 0x39, 0x90, 0xa4, 0x07, 0x05,
|
||||
0x5d, 0x10, 0xf4, 0x94, 0xa0, 0xa7, 0x39, 0xed, 0xa1, 0xcf, 0x11, 0xcb,
|
||||
0x9d, 0x30, 0xef, 0x17, 0x40, 0x86, 0x4e, 0x10, 0xcb, 0x73, 0x7b, 0x9c,
|
||||
0x10, 0xb2, 0xd8, 0xa6, 0x31, 0xba, 0x9f, 0xd3, 0x3e, 0x7a, 0x80, 0xfb,
|
||||
0x6f, 0xcb, 0xc3, 0xd4, 0x4e, 0xc7, 0x39, 0x8d, 0xd3, 0x09, 0x4e, 0xdb,
|
||||
0x68, 0x8e, 0xd3, 0x56, 0x9a, 0xe7, 0x54, 0x21, 0x95, 0xd3, 0x14, 0x2d,
|
||||
0x72, 0xda, 0xcb, 0x69, 0x04, 0x27, 0x44, 0xe6, 0xb4, 0x9b, 0x66, 0x39,
|
||||
0xed, 0xe1, 0x34, 0x0a, 0x4e, 0xe6, 0x74, 0x13, 0x8d, 0x73, 0xda, 0x40,
|
||||
0x87, 0x44, 0x3c, 0x26, 0x38, 0xf5, 0xd0, 0x11, 0x4e, 0xa3, 0xf4, 0x10,
|
||||
0xa7, 0x49, 0x3a, 0xc3, 0x69, 0x82, 0x1e, 0xe6, 0xf1, 0x69, 0xe4, 0xfd,
|
||||
0x63, 0x88, 0xc0, 0x61, 0x4e, 0x43, 0x74, 0x94, 0x53, 0x99, 0x8e, 0x71,
|
||||
0xda, 0x40, 0x9f, 0xe1, 0xd4, 0x5e, 0x4f, 0x4c, 0xac, 0x27, 0x26, 0xd6,
|
||||
0xc1, 0xe8, 0x09, 0x4e, 0xed, 0x75, 0xc4, 0xc4, 0x3a, 0x62, 0x38, 0xb1,
|
||||
0x59, 0x4e, 0x7d, 0x74, 0x92, 0x53, 0x3f, 0x7d, 0x96, 0x53, 0x7b, 0x7d,
|
||||
0x31, 0x9c, 0xe0, 0x45, 0x4e, 0x9b, 0xa9, 0x28, 0xe8, 0x39, 0x4e, 0x83,
|
||||
0x74, 0x9e, 0xd3, 0x00, 0x95, 0x38, 0x6d, 0xa1, 0x65, 0xbe, 0x6f, 0x5d,
|
||||
0xdc, 0xcf, 0x56, 0xec, 0xdb, 0x14, 0xa7, 0x7e, 0x3e, 0x6e, 0x9b, 0x88,
|
||||
0x7b, 0x57, 0x2d, 0xfe, 0x84, 0x9d, 0xb4, 0xcf, 0x30, 0xfb, 0x7c, 0x88,
|
||||
0xcf, 0x9b, 0x28, 0x30, 0x5f, 0x8c, 0xdb, 0xed, 0xb0, 0xd0, 0x07, 0x5d,
|
||||
0xfa, 0x77, 0xa0, 0x7f, 0x52, 0xe8, 0x23, 0x42, 0xce, 0xea, 0xd0, 0x40,
|
||||
0xc0, 0xd6, 0x7f, 0x00, 0xfd, 0x4b, 0x42, 0x1f, 0x15, 0xfd, 0x03, 0xae,
|
||||
0xfe, 0x1e, 0x14, 0xaf, 0xdf, 0x09, 0x7d, 0x4c, 0xe8, 0xbb, 0x5c, 0xfa,
|
||||
0x56, 0xe8, 0x6f, 0x0a, 0x7d, 0xeb, 0x06, 0xfa, 0x3e, 0xe8, 0x8f, 0x24,
|
||||
0xec, 0x76, 0xdb, 0x06, 0xfa, 0xc3, 0xd0, 0x9f, 0x16, 0x7a, 0x1f, 0xd6,
|
||||
0xef, 0x15, 0x3a, 0x56, 0xe3, 0xe6, 0xb0, 0x20, 0x7d, 0x7e, 0x2b, 0x25,
|
||||
0x27, 0x9b, 0xe4, 0x10, 0x02, 0x20, 0xe3, 0x4f, 0x22, 0xc7, 0x16, 0xb5,
|
||||
0x27, 0xcc, 0x5a, 0xa7, 0x24, 0xb6, 0x62, 0x85, 0x6b, 0xd7, 0x74, 0xbc,
|
||||
0x6f, 0xf2, 0x6e, 0x64, 0x44, 0x10, 0x59, 0xa2, 0xd4, 0x6a, 0xa4, 0x3d,
|
||||
0xae, 0x44, 0xfa, 0x40, 0x9a, 0x92, 0x4a, 0x8b, 0x1c, 0x91, 0x89, 0x6b,
|
||||
0x9d, 0xbe, 0x2c, 0xc6, 0x73, 0x92, 0x44, 0x95, 0x30, 0x1b, 0xaf, 0x45,
|
||||
0xd6, 0xc3, 0x64, 0xd3, 0xe4, 0x00, 0x76, 0xbd, 0x49, 0x0a, 0x49, 0xeb,
|
||||
0xed, 0xfd, 0xc2, 0xde, 0xad, 0xbf, 0xdd, 0xcf, 0x26, 0xee, 0xa7, 0x54,
|
||||
0xe7, 0x8b, 0xaf, 0xe6, 0xcb, 0x36, 0xf8, 0xc2, 0x74, 0x9e, 0xf5, 0x3a,
|
||||
0x85, 0xe9, 0xb6, 0x53, 0x4c, 0x0e, 0x22, 0x2e, 0x4c, 0x5b, 0xd7, 0x6f,
|
||||
0x7c, 0x07, 0x62, 0xf3, 0x51, 0xba, 0x7b, 0x84, 0xce, 0x5b, 0xf3, 0xc5,
|
||||
0xf6, 0x42, 0xa6, 0x53, 0xb2, 0x42, 0x0b, 0x8a, 0x07, 0x7e, 0xb1, 0xea,
|
||||
0x15, 0xe4, 0xfe, 0xba, 0x63, 0xb7, 0x59, 0xea, 0xc6, 0x7a, 0x12, 0xc8,
|
||||
0x27, 0xd6, 0x5f, 0xae, 0x8d, 0xdd, 0xc0, 0xc6, 0x1e, 0x50, 0x68, 0x76,
|
||||
0xd0, 0x83, 0xf1, 0x7b, 0x29, 0x3c, 0x11, 0x84, 0xbd, 0x5c, 0xdb, 0x33,
|
||||
0x96, 0x3f, 0xb3, 0xe3, 0x5e, 0x6a, 0x97, 0x25, 0x7a, 0x55, 0x5e, 0x98,
|
||||
0xf0, 0xd2, 0xdc, 0x84, 0x8f, 0x52, 0x4a, 0x84, 0xca, 0xc7, 0xdb, 0x69,
|
||||
0x52, 0x36, 0x06, 0x1a, 0xb1, 0xa6, 0x30, 0xc6, 0xf1, 0xd5, 0xd6, 0xc9,
|
||||
0xea, 0x52, 0x39, 0xd9, 0x80, 0x5a, 0x8b, 0x8b, 0x2a, 0x2d, 0x24, 0xbd,
|
||||
0xb0, 0x8f, 0x51, 0x79, 0x60, 0x0b, 0x4d, 0x7a, 0x4f, 0xf5, 0xfa, 0x48,
|
||||
0x0f, 0x6f, 0x62, 0xf1, 0x57, 0xa2, 0xad, 0x3a, 0x74, 0x93, 0xf0, 0x4b,
|
||||
0xc1, 0x77, 0xd4, 0xab, 0x27, 0x3d, 0xa8, 0x11, 0x7a, 0xd2, 0xc7, 0x65,
|
||||
0x7e, 0xd4, 0xcd, 0x94, 0x12, 0xa2, 0xb9, 0xbb, 0x7c, 0x18, 0xad, 0x99,
|
||||
0x6b, 0x7a, 0x30, 0xe6, 0xe7, 0xc3, 0x9b, 0xe1, 0x7d, 0x0a, 0x73, 0xce,
|
||||
0xf5, 0x31, 0x4d, 0x90, 0x5b, 0x27, 0xa1, 0x49, 0x29, 0xad, 0xb6, 0x6c,
|
||||
0x60, 0x2f, 0x4d, 0xfa, 0x3b, 0x3d, 0x07, 0x11, 0xe9, 0x4e, 0x1a, 0x62,
|
||||
0x97, 0x00, 0xbe, 0x7b, 0xee, 0xfd, 0x9b, 0x0b, 0x33, 0x3f, 0xee, 0x22,
|
||||
0xbb, 0x12, 0x2a, 0xb5, 0x88, 0xb2, 0xeb, 0xf6, 0xdc, 0x03, 0x0d, 0xa8,
|
||||
0x4e, 0xb3, 0xc7, 0xfd, 0xd4, 0x8e, 0x25, 0x0d, 0x4a, 0x92, 0x32, 0x22,
|
||||
0x45, 0x79, 0x9b, 0x65, 0x90, 0x9f, 0x5a, 0xfc, 0x2c, 0x83, 0x18, 0x2d,
|
||||
0x9f, 0xec, 0xa3, 0xb6, 0xc5, 0x16, 0x5f, 0x0f, 0x45, 0x80, 0xa8, 0x2f,
|
||||
0xe2, 0x0b, 0x80, 0x6e, 0xf5, 0xd9, 0xd7, 0xef, 0x4d, 0x7c, 0xce, 0x1e,
|
||||
0x58, 0x7a, 0x6b, 0xf1, 0x66, 0xbe, 0xcc, 0xf6, 0xf9, 0x79, 0x64, 0x06,
|
||||
0x7d, 0x1e, 0xc4, 0x77, 0xd4, 0xa7, 0x20, 0x43, 0xcb, 0xe3, 0x43, 0x34,
|
||||
0x31, 0x1c, 0xe2, 0xb1, 0x77, 0x6c, 0x23, 0x3c, 0xf6, 0xb0, 0xc5, 0x46,
|
||||
0x0e, 0x62, 0x3b, 0x47, 0x65, 0xd8, 0x7b, 0x43, 0xde, 0xf2, 0xf8, 0x4e,
|
||||
0xf2, 0x1c, 0x6f, 0xf2, 0xfe, 0xc9, 0xeb, 0x95, 0x42, 0x5e, 0xe6, 0x09,
|
||||
0xeb, 0x63, 0xef, 0x76, 0xcf, 0xed, 0xfe, 0x2b, 0xf0, 0x1f, 0x99, 0x58,
|
||||
0x1e, 0xef, 0xe7, 0xbe, 0x8e, 0xfa, 0x7c, 0xd4, 0xe9, 0x63, 0x96, 0x11,
|
||||
0xe1, 0x37, 0x6b, 0x39, 0x9e, 0x33, 0xbf, 0xdb, 0xe0, 0x35, 0xbb, 0x6f,
|
||||
0x90, 0xf8, 0x5f, 0x17, 0xcf, 0x60, 0x27, 0x76, 0x01, 0x9e, 0xfb, 0xfb,
|
||||
0xd1, 0x4e, 0x41, 0x63, 0x67, 0xdb, 0xa9, 0x70, 0x03, 0x2d, 0x44, 0xfd,
|
||||
0x7c, 0x6d, 0x4d, 0xb0, 0xf6, 0x09, 0x5b, 0x56, 0xbb, 0x66, 0x55, 0xe1,
|
||||
0x05, 0x49, 0xca, 0x28, 0xc6, 0x09, 0x92, 0xff, 0x61, 0x8f, 0xe9, 0xff,
|
||||
0xe6, 0x8a, 0x2f, 0xc5, 0xa3, 0xe1, 0xe1, 0x23, 0xdb, 0xa3, 0xb7, 0xf0,
|
||||
0x5c, 0x64, 0xbe, 0xb7, 0x43, 0xb0, 0x30, 0xe8, 0x87, 0x8f, 0x41, 0x0a,
|
||||
0x48, 0xf0, 0x4b, 0xf2, 0xf2, 0x7d, 0x63, 0xfe, 0x34, 0xae, 0x8b, 0x51,
|
||||
0xe0, 0xb6, 0x18, 0xb1, 0x39, 0xca, 0xe3, 0x83, 0x88, 0x50, 0x90, 0x9f,
|
||||
0x65, 0xa5, 0x96, 0xcb, 0x1f, 0x61, 0x9b, 0xdd, 0xfd, 0x09, 0x6c, 0xfb,
|
||||
0x84, 0xad, 0x7b, 0x9d, 0x91, 0x8f, 0x58, 0xa7, 0xdf, 0xf4, 0x5f, 0x66,
|
||||
0x2b, 0xdd, 0x23, 0xf6, 0xfd, 0x93, 0xf4, 0x19, 0x16, 0x7d, 0xd6, 0xaa,
|
||||
0x8e, 0x9d, 0x53, 0x73, 0xa8, 0x3b, 0x11, 0xce, 0xcb, 0xeb, 0xeb, 0x55,
|
||||
0xb8, 0x11, 0xa7, 0x60, 0x17, 0x6a, 0xe3, 0x5a, 0xfd, 0x73, 0x7a, 0x36,
|
||||
0x88, 0x3d, 0x63, 0x95, 0xa2, 0x11, 0x95, 0xa2, 0x09, 0x23, 0x4b, 0xae,
|
||||
0xf3, 0xe0, 0xa9, 0xe9, 0x83, 0x5c, 0xd6, 0x20, 0x76, 0xa4, 0x9b, 0x9c,
|
||||
0xfb, 0xd0, 0x00, 0xe7, 0xed, 0xfb, 0x87, 0x26, 0x31, 0x6f, 0x77, 0xad,
|
||||
0xbf, 0x73, 0xee, 0x1d, 0x1b, 0x66, 0xe5, 0x11, 0xd4, 0x5f, 0xab, 0x63,
|
||||
0xac, 0x67, 0x73, 0x6d, 0xb7, 0x3d, 0xb5, 0x39, 0xba, 0x6b, 0xd7, 0x3f,
|
||||
0xe7, 0x3a, 0xa5, 0xd4, 0x7a, 0x3a, 0x7c, 0x13, 0xbf, 0xef, 0x95, 0x85,
|
||||
0x27, 0x8a, 0xa0, 0x32, 0xad, 0x5d, 0x1b, 0x99, 0x2c, 0xca, 0xaf, 0x66,
|
||||
0xb6, 0x2c, 0x26, 0xec, 0x63, 0x62, 0xfc, 0x2e, 0xd1, 0xee, 0x12, 0xfd,
|
||||
0xba, 0xb1, 0x4a, 0x26, 0xef, 0x11, 0xbe, 0x04, 0xc0, 0xf5, 0xf0, 0xdd,
|
||||
0x57, 0xb6, 0x6d, 0xcf, 0x92, 0xef, 0x40, 0x51, 0x2f, 0x5a, 0x07, 0x49,
|
||||
0x3e, 0x38, 0x46, 0x2d, 0x93, 0x85, 0x8a, 0xb1, 0xac, 0x4d, 0x96, 0x8a,
|
||||
0x9a, 0x6e, 0x91, 0x4f, 0x50, 0x69, 0x9a, 0xe4, 0xe9, 0x0c, 0x35, 0x4f,
|
||||
0x57, 0x73, 0xda, 0x49, 0x6d, 0x31, 0x5b, 0xd4, 0x2e, 0x90, 0x94, 0x21,
|
||||
0x25, 0x03, 0xa1, 0x07, 0x5f, 0x19, 0x92, 0x33, 0xac, 0x89, 0xaf, 0x8e,
|
||||
0x8c, 0xaa, 0xe7, 0x2b, 0x46, 0x31, 0x9f, 0xce, 0x19, 0xba, 0x85, 0xce,
|
||||
0xe9, 0x49, 0x46, 0x2f, 0x5a, 0x63, 0x2e, 0xd5, 0x52, 0x45, 0x2d, 0x17,
|
||||
0x8a, 0x39, 0x33, 0x3d, 0x51, 0xb4, 0x96, 0xd5, 0xf2, 0x18, 0x45, 0x6a,
|
||||
0x2a, 0x5d, 0xb3, 0xd2, 0xf3, 0x95, 0xe2, 0x18, 0x75, 0xad, 0x13, 0x15,
|
||||
0x2c, 0xab, 0x9c, 0x9e, 0x35, 0x4b, 0x53, 0x95, 0x8a, 0x51, 0x19, 0xa3,
|
||||
0xd6, 0x9a, 0xd2, 0x30, 0xd3, 0x33, 0x9a, 0x69, 0xaa, 0x4b, 0xda, 0x18,
|
||||
0xf5, 0xd5, 0xa4, 0x17, 0xb4, 0xc5, 0xf3, 0x45, 0x2b, 0x3d, 0xad, 0xae,
|
||||
0xa8, 0x66, 0xae, 0x52, 0x2c, 0x5b, 0xc7, 0xe0, 0x43, 0xe5, 0xac, 0x9a,
|
||||
0x83, 0x55, 0xb2, 0xde, 0xca, 0x19, 0xf6, 0x28, 0xe4, 0x25, 0x0d, 0xa3,
|
||||
0xf7, 0xd4, 0x5b, 0x64, 0xd5, 0x52, 0x55, 0x9b, 0x54, 0x4b, 0xa5, 0x45,
|
||||
0x35, 0x77, 0xfe, 0x4e, 0xfa, 0x03, 0xb7, 0xcf, 0x80, 0x98, 0xb9, 0xe3,
|
||||
0x3a, 0x46, 0xbd, 0x1b, 0x58, 0x9c, 0xd0, 0x4c, 0xa3, 0x5a, 0xc9, 0x69,
|
||||
0x27, 0xb4, 0xcf, 0x57, 0x35, 0xd3, 0xda, 0x60, 0x39, 0xeb, 0x8c, 0xcc,
|
||||
0xb2, 0xa1, 0x9b, 0x58, 0x4e, 0xf7, 0x06, 0x56, 0xb3, 0x9a, 0x65, 0x15,
|
||||
0xf5, 0x25, 0x73, 0x8c, 0x12, 0x1b, 0x68, 0xd9, 0xf6, 0x6d, 0xb0, 0x08,
|
||||
0xa1, 0x71, 0x5c, 0x4c, 0x67, 0x72, 0xc6, 0x72, 0xba, 0xb2, 0x6c, 0x96,
|
||||
0xd2, 0xe7, 0xb0, 0xed, 0x69, 0xd7, 0xde, 0x4f, 0x96, 0x54, 0xd3, 0xd4,
|
||||
0xcc, 0xbe, 0xf5, 0x6b, 0xda, 0xfe, 0x31, 0x3a, 0x08, 0xd3, 0x81, 0x3b,
|
||||
0x9b, 0xba, 0x44, 0x7d, 0x83, 0x63, 0xb4, 0xeb, 0x13, 0xf5, 0x18, 0xa3,
|
||||
0xa1, 0x3b, 0xdb, 0xdf, 0xa7, 0x5a, 0xc5, 0x15, 0xcd, 0x95, 0x18, 0x1f,
|
||||
0xa3, 0x0f, 0x9a, 0x87, 0xca, 0x65, 0x57, 0x9f, 0xd4, 0x1d, 0xfb, 0x30,
|
||||
0x9b, 0xbc, 0x5a, 0x5a, 0x29, 0x9e, 0x4f, 0xab, 0xba, 0x6e, 0x58, 0x98,
|
||||
0xd5, 0xd0, 0xd3, 0x53, 0x7a, 0xae, 0x64, 0x98, 0xd8, 0x23, 0x6e, 0xc5,
|
||||
0x12, 0xe2, 0xff, 0xd8, 0xcc, 0x68, 0x56, 0xc1, 0xc8, 0xb3, 0x2d, 0xbb,
|
||||
0xdd, 0xe8, 0x98, 0xae, 0x6b, 0x15, 0x31, 0xc8, 0x96, 0x0d, 0xf4, 0x33,
|
||||
0xda, 0xf2, 0xa2, 0x30, 0x60, 0xbe, 0x6c, 0xda, 0xc0, 0x64, 0xb6, 0xb8,
|
||||
0xa4, 0xab, 0x56, 0xb5, 0x82, 0xe5, 0x44, 0x33, 0xe7, 0x70, 0x64, 0xd2,
|
||||
0x25, 0x55, 0x5f, 0x4a, 0xdf, 0xbf, 0x78, 0x4e, 0xcb, 0x59, 0xeb, 0x65,
|
||||
0xb3, 0x56, 0x05, 0xfe, 0x8c, 0x51, 0xa8, 0x2e, 0x78, 0x24, 0x65, 0x49,
|
||||
0xce, 0x4e, 0x93, 0x27, 0x3b, 0x8d, 0x92, 0xa0, 0x64, 0x59, 0x5d, 0xc8,
|
||||
0xb2, 0xba, 0xe0, 0x65, 0xdf, 0x28, 0x0f, 0x59, 0xc6, 0xf2, 0x4a, 0xa1,
|
||||
0x64, 0x33, 0x4c, 0x99, 0xc9, 0x4c, 0xf3, 0x6f, 0xa6, 0x5b, 0xa0, 0x50,
|
||||
0x5d, 0x60, 0x49, 0x5a, 0x20, 0x65, 0x01, 0xa3, 0xc8, 0x0b, 0xe8, 0xb1,
|
||||
0xc0, 0x06, 0x5a, 0xc8, 0x2c, 0x80, 0x6f, 0x38, 0x73, 0x66, 0x7a, 0x7e,
|
||||
0x72, 0xea, 0xcc, 0x19, 0x6a, 0x56, 0x73, 0x39, 0x1c, 0xfc, 0x23, 0x25,
|
||||
0x75, 0xc9, 0xa4, 0x76, 0x35, 0x9f, 0xdf, 0xe0, 0xb0, 0x53, 0x38, 0x5f,
|
||||
0x34, 0x51, 0x87, 0x74, 0x2c, 0xc4, 0xf6, 0x98, 0xa2, 0xda, 0x0a, 0x0e,
|
||||
0xab, 0x6a, 0x69, 0x6b, 0xe6, 0xd4, 0x22, 0x64, 0x08, 0xc6, 0xb1, 0x3c,
|
||||
0xc5, 0xd7, 0x5a, 0x38, 0x65, 0xd5, 0x92, 0x25, 0xea, 0x02, 0xb5, 0x2d,
|
||||
0x69, 0xd6, 0x21, 0xfb, 0xcc, 0xcc, 0x9b, 0x5a, 0x65, 0x96, 0x77, 0x36,
|
||||
0xa9, 0x19, 0x62, 0xe7, 0xc0, 0x91, 0x0f, 0x8d, 0xf9, 0x4a, 0x89, 0x92,
|
||||
0x05, 0xde, 0x69, 0x6d, 0x92, 0xa9, 0xba, 0x41, 0xa9, 0xcd, 0xb6, 0xa8,
|
||||
0x3b, 0xf5, 0xe4, 0x29, 0x18, 0xf8, 0x6e, 0x66, 0xdf, 0x87, 0xb5, 0x92,
|
||||
0x66, 0x69, 0x79, 0x6a, 0x60, 0x8d, 0x8c, 0x91, 0x3b, 0x4f, 0x1e, 0x5d,
|
||||
0x5d, 0xd6, 0x28, 0xa4, 0xd7, 0x05, 0x3f, 0x60, 0xe8, 0x93, 0xc8, 0x14,
|
||||
0xed, 0x64, 0x51, 0xcf, 0x1b, 0x17, 0x28, 0x88, 0x66, 0x45, 0xc3, 0x12,
|
||||
0xd7, 0xda, 0x0f, 0xa0, 0x3e, 0x1e, 0x41, 0x95, 0x37, 0x0b, 0x18, 0x2f,
|
||||
0x60, 0xb7, 0x67, 0x2d, 0xb5, 0xc2, 0x86, 0x0f, 0x32, 0x97, 0x72, 0x1a,
|
||||
0x46, 0xe4, 0x65, 0x8c, 0x62, 0xb5, 0x76, 0xfe, 0x28, 0xaa, 0x2e, 0x2f,
|
||||
0x8d, 0x14, 0x5d, 0x13, 0x3a, 0xe5, 0x92, 0x42, 0x65, 0x8c, 0x72, 0x68,
|
||||
0xd1, 0xa8, 0x5a, 0x73, 0x46, 0xc6, 0x50, 0xf3, 0xd4, 0x5c, 0x86, 0xa3,
|
||||
0xa2, 0x1a, 0x53, 0xd4, 0xd5, 0x70, 0x22, 0xd8, 0x6e, 0x6a, 0xd6, 0x44,
|
||||
0xb5, 0x58, 0xc2, 0x0e, 0x2d, 0x18, 0xc6, 0x32, 0xbb, 0x32, 0x54, 0x8c,
|
||||
0x92, 0xc9, 0xe5, 0x87, 0x8b, 0x66, 0xb9, 0xa4, 0x7e, 0x61, 0x9d, 0xbc,
|
||||
0x15, 0x72, 0x16, 0x42, 0x3b, 0xd4, 0x53, 0xba, 0xba, 0x58, 0x82, 0xc3,
|
||||
0x1d, 0x90, 0xce, 0x56, 0xcb, 0x65, 0xa3, 0x62, 0xcd, 0x20, 0x92, 0xc5,
|
||||
0x72, 0x49, 0xac, 0xd4, 0xa4, 0x28, 0x54, 0x6c, 0x6f, 0x0e, 0x2d, 0xa1,
|
||||
0xce, 0xd8, 0xc9, 0xca, 0x65, 0x75, 0x05, 0x98, 0x7a, 0x84, 0xcc, 0xbe,
|
||||
0x44, 0x99, 0x87, 0xb5, 0xc5, 0xea, 0xd2, 0x12, 0x8c, 0x9d, 0x29, 0xc2,
|
||||
0xb6, 0x7e, 0xad, 0x1e, 0xc2, 0xc5, 0x82, 0x51, 0x2d, 0xe5, 0x79, 0xd0,
|
||||
0x73, 0x5a, 0xd9, 0x72, 0xf6, 0x2b, 0x61, 0xcb, 0xef, 0x5f, 0xd1, 0x2a,
|
||||
0x95, 0x62, 0x5e, 0xc3, 0xde, 0xb3, 0x50, 0xb0, 0x79, 0x7d, 0x56, 0xa1,
|
||||
0x68, 0xf6, 0x0d, 0x50, 0x83, 0x65, 0x08, 0x4f, 0x9a, 0xab, 0xae, 0xb4,
|
||||
0xd9, 0x84, 0x84, 0xe8, 0xb3, 0x73, 0xb8, 0x88, 0x39, 0xeb, 0x0f, 0x54,
|
||||
0x9c, 0xa9, 0x73, 0xd5, 0x4a, 0x05, 0x93, 0x4f, 0xb9, 0xd3, 0xd3, 0xbb,
|
||||
0xc2, 0x77, 0xa9, 0xe3, 0x82, 0xe3, 0x5e, 0x6d, 0xe3, 0x1d, 0x97, 0x3a,
|
||||
0x1d, 0x95, 0x2b, 0x09, 0x1c, 0x5d, 0x5c, 0xe8, 0x58, 0x02, 0x30, 0x47,
|
||||
0x6b, 0x49, 0x91, 0x10, 0x8a, 0xdb, 0xb7, 0x3e, 0x5e, 0xa7, 0xa9, 0xed,
|
||||
0xff, 0x33, 0xd2, 0xa5, 0x4b, 0x87, 0x47, 0x1e, 0x49, 0xb1, 0x8b, 0x9e,
|
||||
0xa6, 0xe7, 0x53, 0xfb, 0x53, 0x79, 0xed, 0x62, 0x6a, 0x67, 0x0a, 0x15,
|
||||
0xb1, 0x5c, 0x2c, 0x71, 0x7f, 0xfb, 0x97, 0x8d, 0xbc, 0x06, 0x45, 0x05,
|
||||
0xa9, 0xac, 0x9a, 0x1a, 0x94, 0x05, 0xd5, 0xec, 0xcf, 0x15, 0xb4, 0xdc,
|
||||
0x79, 0xb3, 0xba, 0x6c, 0xa6, 0xf6, 0x9f, 0x55, 0x4b, 0xa6, 0xb6, 0x33,
|
||||
0xb5, 0x5c, 0xd4, 0xfb, 0xd5, 0x72, 0x31, 0xb5, 0x7f, 0x68, 0xf7, 0xce,
|
||||
0x94, 0x59, 0x50, 0xfb, 0x07, 0xd1, 0x89, 0xc5, 0x21, 0x7f, 0x76, 0xcf,
|
||||
0xe0, 0xe2, 0x62, 0x7e, 0x68, 0x64, 0x71, 0x78, 0xef, 0xee, 0xfc, 0xa0,
|
||||
0x36, 0xaa, 0x0d, 0x8c, 0xe6, 0x86, 0xcf, 0xee, 0x1b, 0xca, 0xef, 0xcb,
|
||||
0x0d, 0xe7, 0xd4, 0xe1, 0xd1, 0x91, 0xfc, 0x30, 0x46, 0x45, 0xf4, 0x4d,
|
||||
0x4c, 0x87, 0x4e, 0x23, 0xbb, 0x86, 0x76, 0x0d, 0xf5, 0xe7, 0xb5, 0x95,
|
||||
0xd4, 0x97, 0x48, 0x6e, 0x97, 0x0a, 0x89, 0x36, 0x39, 0x21, 0xdf, 0xeb,
|
||||
0x69, 0x9c, 0x89, 0x7b, 0x1c, 0xc6, 0x2b, 0x77, 0x4a, 0x85, 0x6e, 0x29,
|
||||
0x2e, 0xc9, 0x71, 0xa9, 0xd0, 0xd9, 0xcd, 0xc5, 0x34, 0xd3, 0xc3, 0xa5,
|
||||
0x9e, 0xf8, 0xf6, 0x78, 0x7b, 0x7c, 0x47, 0x5c, 0x71, 0x8c, 0x1b, 0x1c,
|
||||
0xe6, 0x1e, 0x09, 0xb7, 0xe2, 0x82, 0x1f, 0x91, 0x3b, 0x60, 0xeb, 0x4d,
|
||||
0x44, 0x13, 0xad, 0x89, 0x50, 0x22, 0x9c, 0x88, 0x91, 0x84, 0xc7, 0x52,
|
||||
0xb9, 0xf9, 0x2b, 0xab, 0x9e, 0xab, 0xa1, 0x16, 0xe9, 0xa7, 0x21, 0x49,
|
||||
0x5a, 0x0d, 0x41, 0xe6, 0x97, 0xe4, 0x20, 0x64, 0x6f, 0x84, 0x43, 0xd2,
|
||||
0x7b, 0x61, 0xc8, 0x22, 0x92, 0xf4, 0x5d, 0xe0, 0x65, 0xe0, 0x6d, 0xd8,
|
||||
0xdc, 0x02, 0x9e, 0x0d, 0x93, 0x22, 0xc9, 0xf2, 0x53, 0x49, 0x89, 0xff,
|
||||
0xb7, 0xaf, 0xae, 0x7a, 0x6e, 0x44, 0xe2, 0x3f, 0x4b, 0xde, 0x82, 0xcd,
|
||||
0x37, 0xa2, 0x24, 0x4b, 0xb2, 0x17, 0xe8, 0xc0, 0x28, 0x37, 0xa3, 0x5d,
|
||||
0xd2, 0x95, 0x56, 0x49, 0x7a, 0x11, 0xd2, 0xa6, 0x66, 0xbf, 0x2c, 0x45,
|
||||
0xb7, 0x40, 0xfa, 0x7a, 0xbb, 0xf4, 0x28, 0xa6, 0x06, 0x1a, 0x00, 0xc6,
|
||||
0x7b, 0x04, 0x75, 0xa3, 0x57, 0xfa, 0x07, 0x1e, 0x6a, 0xaf, 0xc6, 0x25,
|
||||
0xe9, 0x06, 0x70, 0xb9, 0x0d, 0xbe, 0x24, 0x24, 0xe9, 0x79, 0xe0, 0x6d,
|
||||
0xe0, 0xf1, 0x0e, 0x45, 0x7a, 0x0f, 0xb2, 0x6b, 0x18, 0xfd, 0x7a, 0x1b,
|
||||
0x9b, 0xb3, 0x11, 0x4f, 0xc0, 0x7b, 0x31, 0xfa, 0xad, 0x8e, 0x7d, 0xd2,
|
||||
0x6b, 0x1d, 0xb0, 0xea, 0x60, 0x77, 0x9a, 0xa3, 0x90, 0x3c, 0xdd, 0x69,
|
||||
0xdf, 0x5b, 0x86, 0xe3, 0x44, 0xed, 0xf1, 0x35, 0x7e, 0x73, 0xdc, 0xbe,
|
||||
0x17, 0xdd, 0x16, 0xb7, 0xef, 0x4f, 0x07, 0x40, 0xf7, 0x02, 0x07, 0x5d,
|
||||
0x36, 0x19, 0x17, 0x3f, 0x2f, 0xec, 0x1f, 0x72, 0xc9, 0x54, 0x21, 0x3b,
|
||||
0x07, 0x5a, 0x48, 0xd0, 0xba, 0x8f, 0x55, 0xd7, 0xbe, 0x22, 0xda, 0xce,
|
||||
0x7d, 0x39, 0x7b, 0xc6, 0x5c, 0x85, 0xec, 0xe9, 0x3a, 0xbb, 0x6b, 0x75,
|
||||
0xed, 0x97, 0x45, 0xdb, 0x79, 0x9e, 0x60, 0xbf, 0x85, 0x5e, 0x87, 0x6c,
|
||||
0x44, 0xd0, 0xd7, 0xeb, 0xec, 0x43, 0xb4, 0x7e, 0x1e, 0x46, 0x9d, 0xf7,
|
||||
0x25, 0x6c, 0x0c, 0xe7, 0x9d, 0x09, 0x5b, 0xb3, 0xf3, 0xde, 0x84, 0xdd,
|
||||
0x63, 0x3b, 0xef, 0x4e, 0xd8, 0x5d, 0xb6, 0xf3, 0xfe, 0x84, 0x3d, 0x0f,
|
||||
0x39, 0xef, 0x50, 0xa4, 0xa4, 0xfd, 0xfc, 0xcd, 0xde, 0xa3, 0x48, 0xe1,
|
||||
0xb5, 0xdf, 0xb4, 0xe4, 0xa4, 0x3d, 0x3e, 0x7b, 0xb7, 0xe2, 0x49, 0xda,
|
||||
0xcf, 0x89, 0x2c, 0x36, 0xec, 0x87, 0x76, 0xd6, 0x97, 0xfd, 0x56, 0xa6,
|
||||
0x84, 0xed, 0x67, 0x08, 0x16, 0x23, 0x9f, 0x90, 0xb3, 0xdf, 0xd1, 0xd8,
|
||||
0x43, 0x28, 0xf3, 0x8f, 0xbd, 0xd3, 0xf9, 0x1f, 0x8b, 0x9b, 0x02, 0xf1,
|
||||
0x0c, 0x1a, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (constructor, "<init>", "(Landroid/content/Context;JLjava/lang/String;Ljava/lang/String;)V") \
|
||||
METHOD (canGoBack, "canGoBack", "()Z") \
|
||||
METHOD (goBack, "goBack", "()V") \
|
||||
METHOD (goForward, "goForward", "()V") \
|
||||
METHOD (loadDataWithBaseURL, "loadDataWithBaseURL", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V") \
|
||||
METHOD (loadUrl, "loadUrl", "(Ljava/lang/String;Ljava/util/Map;)V") \
|
||||
METHOD (postUrl, "postUrl", "(Ljava/lang/String;[B)V") \
|
||||
METHOD (reload, "reload", "()V") \
|
||||
METHOD (evaluateJavascript, "evaluateJavascript", "(Ljava/lang/String;)J") \
|
||||
METHOD (stopLoading, "stopLoading", "()V") \
|
||||
METHOD (disconnectNative, "disconnectNative", "()V")
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_BYTECODE (JuceWebView, "com/rmsl/juce/JuceWebViewClasses$JuceWebView", 16, juceWebViewClasses)
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
STATICMETHOD (getInstance, "getInstance", "()Landroid/webkit/CookieManager;")
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidCookieManager, "android/webkit/CookieManager")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (toString, "toString", "()Ljava/lang/String;")
|
||||
|
||||
DECLARE_JNI_CLASS (SslError, "android/net/http/SslError")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
STATICMETHOD (encode, "encode", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;")
|
||||
|
||||
DECLARE_JNI_CLASS (URLEncoder, "java/net/URLEncoder")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
class MessageThreadExecutor : private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
MessageThreadExecutor() = default;
|
||||
|
||||
~MessageThreadExecutor() override
|
||||
{
|
||||
cancelPendingUpdate();
|
||||
}
|
||||
|
||||
void execute (std::function<void()> fn)
|
||||
{
|
||||
const std::lock_guard lock { functionsMutex };
|
||||
functions.emplace_back (std::move (fn));
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
private:
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
const auto popFront = [&]() -> std::function<void()>
|
||||
{
|
||||
const std::lock_guard lock { functionsMutex };
|
||||
|
||||
if (functions.empty())
|
||||
return nullptr;
|
||||
|
||||
const ScopeGuard scope { [&] { functions.pop_front(); } };
|
||||
|
||||
return functions.front();
|
||||
};
|
||||
|
||||
for (auto fn = popFront(); fn != nullptr; fn = popFront())
|
||||
fn();
|
||||
}
|
||||
|
||||
std::deque<std::function<void()>> functions;
|
||||
std::mutex functionsMutex;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (MessageThreadExecutor)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct WebBrowserComponent::Impl::Platform : public AndroidViewComponent,
|
||||
public PlatformInterface,
|
||||
public AsyncUpdater
|
||||
{
|
||||
public:
|
||||
Platform (WebBrowserComponent& o,
|
||||
const WebBrowserComponent::Options& browserOptions,
|
||||
const StringArray& userScripts)
|
||||
: owner (o)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
setView (env->NewObject (JuceWebView,
|
||||
JuceWebView.constructor,
|
||||
getMainActivity().get(),
|
||||
reinterpret_cast<jlong> (this),
|
||||
javaString (browserOptions.getUserAgent()).get(),
|
||||
javaString (userScripts.joinIntoString ("\n")).get()));
|
||||
|
||||
owner.addAndMakeVisible (this);
|
||||
}
|
||||
|
||||
void checkWindowAssociation() override
|
||||
{
|
||||
if (owner.isShowing())
|
||||
{
|
||||
if (owner.blankPageShown)
|
||||
owner.goBack();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (owner.unloadPageWhenHidden && ! owner.blankPageShown)
|
||||
{
|
||||
// when the component becomes invisible, some stuff like flash
|
||||
// carries on playing audio, so we need to force it onto a blank
|
||||
// page to avoid this, (and send it back when it's made visible again).
|
||||
|
||||
owner.blankPageShown = true;
|
||||
goToURL ("about:blank", nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setWebViewSize (int width, int height) override
|
||||
{
|
||||
setSize (width, height);
|
||||
}
|
||||
|
||||
void evaluateJavascript (const String& script, WebBrowserComponent::EvaluationCallback callback) override
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_THREAD
|
||||
|
||||
auto* env = getEnv();
|
||||
auto view = (jobject) getView();
|
||||
|
||||
const auto resultId = (int64) env->CallLongMethod (view,
|
||||
JuceWebView.evaluateJavascript,
|
||||
javaString (script).get());
|
||||
|
||||
if (callback != nullptr)
|
||||
evaluationCallbacks[resultId] = std::move (callback);
|
||||
}
|
||||
|
||||
~Platform() override
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
env->CallVoidMethod ((jobject) getView(), JuceWebView.stopLoading);
|
||||
|
||||
masterReference.clear();
|
||||
|
||||
// if other Java thread is waiting for us to respond to page load request
|
||||
// wake it up immediately (false answer will be sent), so that it releases
|
||||
// the lock we need when calling hostDeleted.
|
||||
responseReadyEvent.signal();
|
||||
|
||||
resourceRequester.cancelAllResourceRequests();
|
||||
|
||||
env->CallVoidMethod ((jobject) getView(), JuceWebView.disconnectNative);
|
||||
}
|
||||
|
||||
void goToURL (const String& url,
|
||||
const StringArray* headers,
|
||||
const MemoryBlock* postData) override
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
if (headers == nullptr && postData == nullptr)
|
||||
{
|
||||
env->CallVoidMethod ((jobject) getView(), JuceWebView.loadUrl, javaString (url).get(), 0);
|
||||
}
|
||||
else if (headers != nullptr && postData == nullptr)
|
||||
{
|
||||
auto headersMap = LocalRef<jobject> (env->NewObject (JavaHashMap,
|
||||
JavaHashMap.constructorWithCapacity,
|
||||
headers->size()));
|
||||
|
||||
for (const auto& header : *headers)
|
||||
{
|
||||
auto name = header.upToFirstOccurrenceOf (":", false, false).trim();
|
||||
auto value = header.fromFirstOccurrenceOf (":", false, false).trim();
|
||||
|
||||
env->CallObjectMethod (headersMap, JavaMap.put,
|
||||
javaString (name).get(),
|
||||
javaString (value).get());
|
||||
}
|
||||
|
||||
env->CallVoidMethod ((jobject) getView(), JuceWebView.loadUrl,
|
||||
javaString (url).get(), headersMap.get());
|
||||
}
|
||||
else if (headers == nullptr && postData != nullptr)
|
||||
{
|
||||
auto dataStringJuce = postData->toString();
|
||||
auto dataStringJava = javaString (dataStringJuce);
|
||||
auto encodingString = LocalRef<jobject> (env->CallStaticObjectMethod (URLEncoder, URLEncoder.encode,
|
||||
dataStringJava.get(), javaString ("utf-8").get()));
|
||||
|
||||
auto bytes = LocalRef<jbyteArray> ((jbyteArray) env->CallObjectMethod (encodingString, JavaString.getBytes));
|
||||
|
||||
env->CallVoidMethod ((jobject) getView(), JuceWebView.postUrl,
|
||||
javaString (url).get(), bytes.get());
|
||||
}
|
||||
else if (headers != nullptr && postData != nullptr)
|
||||
{
|
||||
// There is no support for both extra headers and post data in Android WebView, so
|
||||
// we need to open URL manually.
|
||||
|
||||
URL urlToUse = URL (url).withPOSTData (*postData);
|
||||
connectionThread.reset (new ConnectionThread (*this, urlToUse, *headers));
|
||||
}
|
||||
}
|
||||
|
||||
void stop() override
|
||||
{
|
||||
connectionThread = nullptr;
|
||||
|
||||
getEnv()->CallVoidMethod ((jobject) getView(), JuceWebView.stopLoading);
|
||||
}
|
||||
|
||||
void goBack() override
|
||||
{
|
||||
connectionThread = nullptr;
|
||||
|
||||
auto* env = getEnv();
|
||||
auto view = (jobject) getView();
|
||||
|
||||
if (env->CallBooleanMethod (view, JuceWebView.canGoBack))
|
||||
env->CallVoidMethod (view, JuceWebView.goBack);
|
||||
else
|
||||
owner.reloadLastURL();
|
||||
}
|
||||
|
||||
void goForward() override
|
||||
{
|
||||
connectionThread = nullptr;
|
||||
|
||||
getEnv()->CallVoidMethod ((jobject) getView(), JuceWebView.goForward);
|
||||
}
|
||||
|
||||
void refresh() override
|
||||
{
|
||||
connectionThread = nullptr;
|
||||
|
||||
getEnv()->CallVoidMethod ((jobject) getView(), JuceWebView.reload);
|
||||
}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
jassert (connectionThread != nullptr);
|
||||
|
||||
if (connectionThread == nullptr)
|
||||
return;
|
||||
|
||||
auto& result = connectionThread->getResult();
|
||||
|
||||
if (result.statusCode >= 200 && result.statusCode < 300)
|
||||
{
|
||||
auto url = javaString (result.url);
|
||||
auto data = javaString (result.data);
|
||||
auto mimeType = javaString ("text/html");
|
||||
auto encoding = javaString ("utf-8");
|
||||
|
||||
getEnv()->CallVoidMethod ((jobject) getView(), JuceWebView.loadDataWithBaseURL,
|
||||
url.get(), data.get(), mimeType.get(),
|
||||
encoding.get(), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
owner.pageLoadHadNetworkError (result.description);
|
||||
}
|
||||
}
|
||||
|
||||
bool handlePageAboutToLoad (const String& url)
|
||||
{
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
return owner.pageAboutToLoad (url);
|
||||
|
||||
WeakReference<Platform> weakRef (this);
|
||||
|
||||
if (weakRef == nullptr)
|
||||
return false;
|
||||
|
||||
responseReadyEvent.reset();
|
||||
|
||||
bool shouldLoad = false;
|
||||
|
||||
MessageManager::callAsync ([weakRef, url, &shouldLoad]
|
||||
{
|
||||
if (weakRef == nullptr)
|
||||
return;
|
||||
|
||||
shouldLoad = weakRef->owner.pageAboutToLoad (url);
|
||||
|
||||
weakRef->responseReadyEvent.signal();
|
||||
});
|
||||
|
||||
responseReadyEvent.wait (-1);
|
||||
|
||||
return shouldLoad;
|
||||
}
|
||||
|
||||
WebBrowserComponent& owner;
|
||||
|
||||
private:
|
||||
class ConnectionThread final : private Thread
|
||||
{
|
||||
public:
|
||||
struct Result
|
||||
{
|
||||
String url;
|
||||
int statusCode = 0;
|
||||
String description;
|
||||
String data;
|
||||
};
|
||||
|
||||
ConnectionThread (Platform& ownerToUse,
|
||||
URL& url,
|
||||
const StringArray& headers)
|
||||
: Thread (SystemStats::getJUCEVersion() + ": WebBrowserComponent::Platform::ConnectionThread"),
|
||||
owner (ownerToUse),
|
||||
webInputStream (new WebInputStream (url, true))
|
||||
{
|
||||
webInputStream->withExtraHeaders (headers.joinIntoString ("\n"));
|
||||
webInputStream->withConnectionTimeout (10000);
|
||||
|
||||
result.url = url.toString (true);
|
||||
|
||||
startThread();
|
||||
}
|
||||
|
||||
~ConnectionThread() override
|
||||
{
|
||||
webInputStream->cancel();
|
||||
signalThreadShouldExit();
|
||||
waitForThreadToExit (10000);
|
||||
|
||||
webInputStream = nullptr;
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
if (! webInputStream->connect (nullptr))
|
||||
{
|
||||
result.description = "Could not establish connection";
|
||||
owner.triggerAsyncUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
result.statusCode = webInputStream->getStatusCode();
|
||||
result.description = "Status code: " + String (result.statusCode);
|
||||
readFromInputStream();
|
||||
owner.triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
const Result& getResult() { return result; }
|
||||
|
||||
private:
|
||||
void readFromInputStream()
|
||||
{
|
||||
MemoryOutputStream ostream;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (threadShouldExit())
|
||||
return;
|
||||
|
||||
char buffer [8192];
|
||||
auto num = webInputStream->read (buffer, sizeof (buffer));
|
||||
|
||||
if (num <= 0)
|
||||
break;
|
||||
|
||||
ostream.write (buffer, (size_t) num);
|
||||
}
|
||||
|
||||
result.data = ostream.toUTF8();
|
||||
}
|
||||
|
||||
Platform& owner;
|
||||
std::unique_ptr<WebInputStream> webInputStream;
|
||||
Result result;
|
||||
};
|
||||
|
||||
class MessageThreadResourceRequester : private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
explicit MessageThreadResourceRequester (Platform& ownerIn)
|
||||
: owner (ownerIn)
|
||||
{}
|
||||
|
||||
~MessageThreadResourceRequester() override
|
||||
{
|
||||
cancelPendingUpdate();
|
||||
}
|
||||
|
||||
// The Platform destructor will block for as long as a Java thread waits for a Resource, so this
|
||||
// must be called from ~Platform().
|
||||
void cancelAllResourceRequests()
|
||||
{
|
||||
std::lock_guard lock { queueMutex };
|
||||
|
||||
while (! queue.empty())
|
||||
{
|
||||
auto* r = queue.front();
|
||||
queue.pop_front();
|
||||
r->event.signal();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Resource> handleResourceRequestOnMessageThread (StringRef uri)
|
||||
{
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
return owner.owner.impl->handleResourceRequest (uri);
|
||||
|
||||
WaitableResource resource { uri };
|
||||
|
||||
{
|
||||
std::lock_guard lock { queueMutex };
|
||||
queue.push_back (&resource);
|
||||
}
|
||||
|
||||
triggerAsyncUpdate();
|
||||
resource.event.wait (-1);
|
||||
|
||||
return resource.resource;
|
||||
}
|
||||
|
||||
private:
|
||||
struct WaitableResource
|
||||
{
|
||||
explicit WaitableResource (StringRef uriIn) : uri (uriIn) {}
|
||||
|
||||
String uri;
|
||||
WaitableEvent event;
|
||||
std::optional<WebBrowserComponent::Resource> resource;
|
||||
};
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
const auto popFront = [&]() -> WaitableResource*
|
||||
{
|
||||
const std::lock_guard lock { queueMutex };
|
||||
|
||||
if (queue.empty())
|
||||
return nullptr;
|
||||
|
||||
const ScopeGuard scope { [&] { queue.pop_front(); } };
|
||||
|
||||
return queue.front();
|
||||
};
|
||||
|
||||
for (auto* r = popFront(); r != nullptr; r = popFront())
|
||||
{
|
||||
r->resource = (owner.owner.impl->handleResourceRequest (r->uri));
|
||||
r->event.signal();
|
||||
}
|
||||
}
|
||||
|
||||
Platform& owner;
|
||||
std::deque<WaitableResource*> queue;
|
||||
std::mutex queueMutex;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (constructor, "<init>", "(J)V") \
|
||||
METHOD (hostDeleted, "hostDeleted", "()V") \
|
||||
CALLBACK (generatedCallback<&Platform::webViewReceivedHttpError>, "webViewReceivedHttpError", "(JLandroid/webkit/WebView;Landroid/webkit/WebResourceRequest;Landroid/webkit/WebResourceResponse;)V") \
|
||||
CALLBACK (generatedCallback<&Platform::handleResourceRequest>, "handleResourceRequest", "(JLandroid/webkit/WebView;Ljava/lang/String;)Landroid/webkit/WebResourceResponse;") \
|
||||
CALLBACK (generatedCallback<&Platform::webViewPageLoadFinished>, "webViewPageLoadFinished", "(JLandroid/webkit/WebView;Ljava/lang/String;)V") \
|
||||
CALLBACK (generatedCallback<&Platform::webViewReceivedSslError>, "webViewReceivedSslError", "(JLandroid/webkit/WebView;Landroid/webkit/SslErrorHandler;Landroid/net/http/SslError;)V") \
|
||||
CALLBACK (generatedCallback<&Platform::webViewCloseWindowRequest>, "webViewCloseWindowRequest", "(JLandroid/webkit/WebView;)V") \
|
||||
CALLBACK (generatedCallback<&Platform::webViewCreateWindowRequest>, "webViewCreateWindowRequest", "(JLandroid/webkit/WebView;)V") \
|
||||
CALLBACK (generatedCallback<&Platform::evaluationResultHandler>, "evaluationResultHandler", "(JJLjava/lang/String;)V") \
|
||||
CALLBACK (generatedCallback<&Platform::postMessageHandler>, "postMessage", "(JLjava/lang/String;)Ljava/lang/String;") \
|
||||
CALLBACK (generatedCallback<&Platform::pageAboutToLoad>, "pageAboutToLoad", "(JLjava/lang/String;)Z")
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_BYTECODE (NativeInterface, "com/rmsl/juce/JuceWebViewClasses$NativeInterface", 16, juceWebViewClasses)
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (create, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/io/InputStream;)V") \
|
||||
METHOD (setResponseHeaders, "setResponseHeaders", "(Ljava/util/Map;)V") \
|
||||
|
||||
DECLARE_JNI_CLASS (WebResourceResponse, "android/webkit/WebResourceResponse")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (create, "<init>", "([B)V") \
|
||||
|
||||
DECLARE_JNI_CLASS (ByteArrayInputStream, "java/io/ByteArrayInputStream")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
static jobject handleResourceRequest (JNIEnv* env, Platform& t, jobject /*webView*/, jstring url)
|
||||
{
|
||||
const auto uriString = juceString (url);
|
||||
|
||||
if (const auto resourceRequestUri = uriString.fromFirstOccurrenceOf ("https://juce.backend", false, false);
|
||||
resourceRequestUri.isNotEmpty())
|
||||
{
|
||||
if (auto response = t.resourceRequester.handleResourceRequestOnMessageThread (resourceRequestUri))
|
||||
{
|
||||
jbyteArray byteArray = env->NewByteArray ((jsize) response->data.size());
|
||||
env->SetByteArrayRegion (byteArray, 0, (jsize) response->data.size(), (jbyte*) response->data.data());
|
||||
LocalRef<jobject> byteArrayInputStream { env->NewObject (ByteArrayInputStream, ByteArrayInputStream.create, byteArray) };
|
||||
auto resource = env->NewObject (WebResourceResponse,
|
||||
WebResourceResponse.create,
|
||||
javaString (response->mimeType).get(),
|
||||
response->mimeType.contains ("text") ? javaString ("utf-8").get() : nullptr,
|
||||
byteArrayInputStream.get());
|
||||
|
||||
if (const auto allowedOrigin = t.owner.impl->options.getAllowedOrigin())
|
||||
{
|
||||
LocalRef<jobject> headersMap { env->NewObject (JavaHashMap,
|
||||
JavaHashMap.constructorWithCapacity,
|
||||
1) };
|
||||
|
||||
env->CallObjectMethod (headersMap, JavaMap.put,
|
||||
javaString ("Access-Control-Allow-Origin").get(),
|
||||
javaString (*allowedOrigin).get());
|
||||
|
||||
env->CallVoidMethod (resource,
|
||||
WebResourceResponse.setResponseHeaders,
|
||||
headersMap.get());
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void webViewPageLoadFinished (JNIEnv*, Platform& t, jobject /*webView*/, jstring url)
|
||||
{
|
||||
t.owner.pageFinishedLoading (juceString (url));
|
||||
}
|
||||
|
||||
static void webViewReceivedSslError (JNIEnv* env, Platform& t, jobject /*webView*/, jobject /*sslErrorHandler*/, jobject sslError)
|
||||
{
|
||||
const auto errorString = LocalRef<jstring> ((jstring) env->CallObjectMethod (sslError, SslError.toString));
|
||||
t.owner.pageLoadHadNetworkError (juceString (errorString));
|
||||
}
|
||||
|
||||
static void evaluationResultHandler (JNIEnv*, Platform& t, jlong evalId, jstring result)
|
||||
{
|
||||
auto it = t.evaluationCallbacks.find ((int64) evalId);
|
||||
|
||||
if (it == t.evaluationCallbacks.end())
|
||||
return;
|
||||
|
||||
(it->second) (EvaluationResult { JSON::fromString (juceString (result)) });
|
||||
|
||||
t.evaluationCallbacks.erase (it);
|
||||
}
|
||||
|
||||
static jobject postMessageHandler (JNIEnv*, Platform& t, jstring message)
|
||||
{
|
||||
t.messageThreadExecutor.execute ([&t, msg = juceString (message)]
|
||||
{
|
||||
var obj;
|
||||
|
||||
if (! JSON::parse (msg, obj).wasOk())
|
||||
{
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
t.owner.impl->handleNativeEvent (obj);
|
||||
});
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool pageAboutToLoad (JNIEnv*, Platform& t, jstring url)
|
||||
{
|
||||
return t.handlePageAboutToLoad (juceString (url));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static void webViewCloseWindowRequest (JNIEnv*, Platform& t, jobject)
|
||||
{
|
||||
t.owner.windowCloseRequest();
|
||||
}
|
||||
|
||||
static void webViewCreateWindowRequest (JNIEnv*, Platform& t, jobject)
|
||||
{
|
||||
t.owner.newWindowAttemptingToLoad ({});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static void webViewReceivedHttpError (JNIEnv* env, Platform& t, jobject /*webView*/, jobject /*request*/, jobject errorResponse)
|
||||
{
|
||||
LocalRef<jclass> responseClass (env->FindClass ("android/webkit/WebResourceResponse"));
|
||||
|
||||
if (responseClass != nullptr)
|
||||
{
|
||||
jmethodID method = env->GetMethodID (responseClass, "getReasonPhrase", "()Ljava/lang/String;");
|
||||
|
||||
if (method != nullptr)
|
||||
{
|
||||
auto errorString = LocalRef<jstring> ((jstring) env->CallObjectMethod (errorResponse, method));
|
||||
|
||||
t.owner.pageLoadHadNetworkError (juceString (errorString));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Should never get here!
|
||||
jassertfalse;
|
||||
t.owner.pageLoadHadNetworkError ({});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<ConnectionThread> connectionThread;
|
||||
WaitableEvent responseReadyEvent;
|
||||
std::unordered_map<int64, EvaluationCallback> evaluationCallbacks;
|
||||
MessageThreadExecutor messageThreadExecutor;
|
||||
MessageThreadResourceRequester resourceRequester { *this };
|
||||
|
||||
WeakReference<Platform>::Master masterReference;
|
||||
friend class WeakReference<Platform>;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
auto WebBrowserComponent::Impl::createAndInitPlatformDependentPart (WebBrowserComponent::Impl& impl,
|
||||
const WebBrowserComponent::Options& options,
|
||||
const StringArray& userScripts)
|
||||
-> std::unique_ptr<PlatformInterface>
|
||||
{
|
||||
return std::make_unique<Platform> (impl.owner, options, userScripts);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void WebBrowserComponent::clearCookies()
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
auto cookieManager = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidCookieManager,
|
||||
AndroidCookieManager.getInstance));
|
||||
|
||||
jmethodID clearCookiesMethod = env->GetMethodID (AndroidCookieManager, "removeAllCookies", "(Landroid/webkit/ValueCallback;)V");
|
||||
env->CallVoidMethod (cookieManager, clearCookiesMethod, 0);
|
||||
}
|
||||
|
||||
bool WebBrowserComponent::areOptionsSupported (const Options& options)
|
||||
{
|
||||
return (options.getBackend() == Options::Backend::defaultBackend);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1316
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,739 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
::Window juce_createKeyProxyWindow (ComponentPeer*);
|
||||
void juce_deleteKeyProxyWindow (::Window);
|
||||
|
||||
//==============================================================================
|
||||
enum
|
||||
{
|
||||
maxXEmbedVersionToSupport = 0
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
XEMBED_MAPPED = (1<<0)
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
XEMBED_EMBEDDED_NOTIFY = 0,
|
||||
XEMBED_WINDOW_ACTIVATE = 1,
|
||||
XEMBED_WINDOW_DEACTIVATE = 2,
|
||||
XEMBED_REQUEST_FOCUS = 3,
|
||||
XEMBED_FOCUS_IN = 4,
|
||||
XEMBED_FOCUS_OUT = 5,
|
||||
XEMBED_FOCUS_NEXT = 6,
|
||||
XEMBED_FOCUS_PREV = 7,
|
||||
XEMBED_MODALITY_ON = 10,
|
||||
XEMBED_MODALITY_OFF = 11,
|
||||
XEMBED_REGISTER_ACCELERATOR = 12,
|
||||
XEMBED_UNREGISTER_ACCELERATOR = 13,
|
||||
XEMBED_ACTIVATE_ACCELERATOR = 14
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
XEMBED_FOCUS_CURRENT = 0,
|
||||
XEMBED_FOCUS_FIRST = 1,
|
||||
XEMBED_FOCUS_LAST = 2
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class XEmbedComponent::Pimpl : private ComponentListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
struct SharedKeyWindow final : public ReferenceCountedObject
|
||||
{
|
||||
SharedKeyWindow (ComponentPeer* peerToUse)
|
||||
: keyPeer (peerToUse),
|
||||
keyProxy (juce_createKeyProxyWindow (keyPeer)),
|
||||
association (peerToUse, keyProxy)
|
||||
{}
|
||||
|
||||
~SharedKeyWindow()
|
||||
{
|
||||
association = {};
|
||||
juce_deleteKeyProxyWindow (keyProxy);
|
||||
|
||||
auto& keyWindows = getKeyWindows();
|
||||
keyWindows.remove (keyPeer);
|
||||
}
|
||||
|
||||
using Ptr = ReferenceCountedObjectPtr<SharedKeyWindow>;
|
||||
|
||||
//==============================================================================
|
||||
Window getHandle() { return keyProxy; }
|
||||
|
||||
static Window getCurrentFocusWindow (ComponentPeer* peerToLookFor)
|
||||
{
|
||||
auto& keyWindows = getKeyWindows();
|
||||
|
||||
if (peerToLookFor != nullptr)
|
||||
if (auto* foundKeyWindow = keyWindows[peerToLookFor])
|
||||
return foundKeyWindow->keyProxy;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static SharedKeyWindow::Ptr getKeyWindowForPeer (ComponentPeer* peerToLookFor)
|
||||
{
|
||||
jassert (peerToLookFor != nullptr);
|
||||
|
||||
auto& keyWindows = getKeyWindows();
|
||||
auto foundKeyWindow = keyWindows[peerToLookFor];
|
||||
|
||||
if (foundKeyWindow == nullptr)
|
||||
{
|
||||
foundKeyWindow = new SharedKeyWindow (peerToLookFor);
|
||||
keyWindows.set (peerToLookFor, foundKeyWindow);
|
||||
}
|
||||
|
||||
return foundKeyWindow;
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
ComponentPeer* keyPeer;
|
||||
Window keyProxy;
|
||||
ScopedWindowAssociation association;
|
||||
|
||||
static HashMap<ComponentPeer*, SharedKeyWindow*>& getKeyWindows()
|
||||
{
|
||||
// store a weak reference to the shared key windows
|
||||
static HashMap<ComponentPeer*, SharedKeyWindow*> keyWindows;
|
||||
return keyWindows;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
//==============================================================================
|
||||
Pimpl (XEmbedComponent& parent, Window x11Window,
|
||||
bool wantsKeyboardFocus, bool isClientInitiated, bool shouldAllowResize)
|
||||
: owner (parent),
|
||||
infoAtom (XWindowSystem::getInstance()->getAtoms().XembedInfo),
|
||||
messageTypeAtom (XWindowSystem::getInstance()->getAtoms().XembedMsgType),
|
||||
clientInitiated (isClientInitiated),
|
||||
wantsFocus (wantsKeyboardFocus),
|
||||
allowResize (shouldAllowResize)
|
||||
{
|
||||
getWidgets().add (this);
|
||||
|
||||
createHostWindow();
|
||||
|
||||
if (clientInitiated)
|
||||
setClient (x11Window, true);
|
||||
|
||||
owner.setWantsKeyboardFocus (wantsFocus);
|
||||
owner.addComponentListener (this);
|
||||
}
|
||||
|
||||
~Pimpl() override
|
||||
{
|
||||
owner.removeComponentListener (this);
|
||||
setClient (0, true);
|
||||
|
||||
if (host != 0)
|
||||
{
|
||||
auto dpy = getDisplay();
|
||||
|
||||
X11Symbols::getInstance()->xDestroyWindow (dpy, host);
|
||||
X11Symbols::getInstance()->xSync (dpy, false);
|
||||
|
||||
auto mask = NoEventMask | KeyPressMask | KeyReleaseMask
|
||||
| EnterWindowMask | LeaveWindowMask | PointerMotionMask
|
||||
| KeymapStateMask | ExposureMask | StructureNotifyMask
|
||||
| FocusChangeMask;
|
||||
|
||||
XEvent event;
|
||||
while (X11Symbols::getInstance()->xCheckWindowEvent (dpy, host, mask, &event) == True)
|
||||
{}
|
||||
|
||||
host = 0;
|
||||
}
|
||||
|
||||
getWidgets().removeAllInstancesOf (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void setClient (Window xembedClient, bool shouldReparent)
|
||||
{
|
||||
removeClient();
|
||||
|
||||
if (xembedClient != 0)
|
||||
{
|
||||
auto dpy = getDisplay();
|
||||
|
||||
client = xembedClient;
|
||||
|
||||
// if the client has initiated the component then keep the clients size
|
||||
// otherwise the client should use the host's window' size
|
||||
if (clientInitiated)
|
||||
{
|
||||
configureNotify();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto newBounds = getX11BoundsFromJuce();
|
||||
X11Symbols::getInstance()->xResizeWindow (dpy, client, static_cast<unsigned int> (newBounds.getWidth()),
|
||||
static_cast<unsigned int> (newBounds.getHeight()));
|
||||
}
|
||||
|
||||
auto eventMask = StructureNotifyMask | PropertyChangeMask | FocusChangeMask;
|
||||
|
||||
XWindowAttributes clientAttr;
|
||||
X11Symbols::getInstance()->xGetWindowAttributes (dpy, client, &clientAttr);
|
||||
|
||||
if ((eventMask & clientAttr.your_event_mask) != eventMask)
|
||||
X11Symbols::getInstance()->xSelectInput (dpy, client, clientAttr.your_event_mask | eventMask);
|
||||
|
||||
getXEmbedMappedFlag();
|
||||
|
||||
if (shouldReparent)
|
||||
X11Symbols::getInstance()->xReparentWindow (dpy, client, host, 0, 0);
|
||||
|
||||
if (supportsXembed)
|
||||
sendXEmbedEvent (CurrentTime, XEMBED_EMBEDDED_NOTIFY, 0, (long) host, xembedVersion);
|
||||
|
||||
updateMapping();
|
||||
}
|
||||
}
|
||||
|
||||
void focusGained (FocusChangeType changeType, FocusChangeDirection direction)
|
||||
{
|
||||
if (client != 0 && supportsXembed && wantsFocus)
|
||||
{
|
||||
updateKeyFocus();
|
||||
|
||||
const auto xembedDirection = [&]
|
||||
{
|
||||
if (direction == FocusChangeDirection::forward)
|
||||
return XEMBED_FOCUS_FIRST;
|
||||
|
||||
if (direction == FocusChangeDirection::backward)
|
||||
return XEMBED_FOCUS_LAST;
|
||||
|
||||
return XEMBED_FOCUS_CURRENT;
|
||||
}();
|
||||
|
||||
sendXEmbedEvent (CurrentTime, XEMBED_FOCUS_IN,
|
||||
(changeType == focusChangedByTabKey ? xembedDirection : XEMBED_FOCUS_CURRENT));
|
||||
}
|
||||
}
|
||||
|
||||
void focusLost (FocusChangeType)
|
||||
{
|
||||
if (client != 0 && supportsXembed && wantsFocus)
|
||||
{
|
||||
sendXEmbedEvent (CurrentTime, XEMBED_FOCUS_OUT);
|
||||
updateKeyFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void broughtToFront()
|
||||
{
|
||||
if (client != 0 && supportsXembed)
|
||||
sendXEmbedEvent (CurrentTime, XEMBED_WINDOW_ACTIVATE);
|
||||
}
|
||||
|
||||
unsigned long getHostWindowID()
|
||||
{
|
||||
// You are using the client initiated version of the protocol. You cannot
|
||||
// retrieve the window id of the host. Please read the documentation for
|
||||
// the XEmebedComponent class.
|
||||
jassert (! clientInitiated);
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
void updateEmbeddedBounds()
|
||||
{
|
||||
componentMovedOrResized (owner, true, true);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
XEmbedComponent& owner;
|
||||
Window client = 0, host = 0;
|
||||
Atom infoAtom, messageTypeAtom;
|
||||
|
||||
bool clientInitiated;
|
||||
bool wantsFocus = false;
|
||||
bool allowResize = false;
|
||||
bool supportsXembed = false;
|
||||
bool hasBeenMapped = false;
|
||||
int xembedVersion = maxXEmbedVersionToSupport;
|
||||
|
||||
ComponentPeer* lastPeer = nullptr;
|
||||
SharedKeyWindow::Ptr keyWindow;
|
||||
|
||||
//==============================================================================
|
||||
void componentParentHierarchyChanged (Component&) override { peerChanged (owner.getPeer()); }
|
||||
void componentMovedOrResized (Component&, bool, bool) override
|
||||
{
|
||||
if (host != 0 && lastPeer != nullptr)
|
||||
{
|
||||
auto dpy = getDisplay();
|
||||
auto newBounds = getX11BoundsFromJuce();
|
||||
XWindowAttributes attr;
|
||||
|
||||
if (X11Symbols::getInstance()->xGetWindowAttributes (dpy, host, &attr))
|
||||
{
|
||||
Rectangle<int> currentBounds (attr.x, attr.y, attr.width, attr.height);
|
||||
if (currentBounds != newBounds)
|
||||
{
|
||||
X11Symbols::getInstance()->xMoveResizeWindow (dpy, host, newBounds.getX(), newBounds.getY(),
|
||||
static_cast<unsigned int> (newBounds.getWidth()),
|
||||
static_cast<unsigned int> (newBounds.getHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
if (client != 0 && X11Symbols::getInstance()->xGetWindowAttributes (dpy, client, &attr))
|
||||
{
|
||||
Rectangle<int> currentBounds (attr.x, attr.y, attr.width, attr.height);
|
||||
|
||||
if ((currentBounds.getWidth() != newBounds.getWidth()
|
||||
|| currentBounds.getHeight() != newBounds.getHeight()))
|
||||
{
|
||||
X11Symbols::getInstance()->xMoveResizeWindow (dpy, client, 0, 0,
|
||||
static_cast<unsigned int> (newBounds.getWidth()),
|
||||
static_cast<unsigned int> (newBounds.getHeight()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void createHostWindow()
|
||||
{
|
||||
auto dpy = getDisplay();
|
||||
int defaultScreen = X11Symbols::getInstance()->xDefaultScreen (dpy);
|
||||
Window root = X11Symbols::getInstance()->xRootWindow (dpy, defaultScreen);
|
||||
|
||||
XSetWindowAttributes swa;
|
||||
swa.border_pixel = 0;
|
||||
swa.background_pixmap = None;
|
||||
swa.override_redirect = True;
|
||||
swa.event_mask = SubstructureNotifyMask | StructureNotifyMask | FocusChangeMask;
|
||||
|
||||
host = X11Symbols::getInstance()->xCreateWindow (dpy, root, 0, 0, 1, 1, 0, CopyFromParent,
|
||||
InputOutput, CopyFromParent,
|
||||
CWEventMask | CWBorderPixel | CWBackPixmap | CWOverrideRedirect,
|
||||
&swa);
|
||||
}
|
||||
|
||||
void removeClient()
|
||||
{
|
||||
if (client != 0)
|
||||
{
|
||||
auto dpy = getDisplay();
|
||||
X11Symbols::getInstance()->xSelectInput (dpy, client, 0);
|
||||
|
||||
keyWindow = nullptr;
|
||||
|
||||
int defaultScreen = X11Symbols::getInstance()->xDefaultScreen (dpy);
|
||||
Window root = X11Symbols::getInstance()->xRootWindow (dpy, defaultScreen);
|
||||
|
||||
if (hasBeenMapped)
|
||||
{
|
||||
X11Symbols::getInstance()->xUnmapWindow (dpy, client);
|
||||
hasBeenMapped = false;
|
||||
}
|
||||
|
||||
X11Symbols::getInstance()->xReparentWindow (dpy, client, root, 0, 0);
|
||||
client = 0;
|
||||
|
||||
X11Symbols::getInstance()->xSync (dpy, False);
|
||||
}
|
||||
}
|
||||
|
||||
void updateMapping()
|
||||
{
|
||||
if (client != 0)
|
||||
{
|
||||
const bool shouldBeMapped = getXEmbedMappedFlag();
|
||||
|
||||
if (shouldBeMapped != hasBeenMapped)
|
||||
{
|
||||
hasBeenMapped = shouldBeMapped;
|
||||
|
||||
if (shouldBeMapped)
|
||||
X11Symbols::getInstance()->xMapWindow (getDisplay(), client);
|
||||
else
|
||||
X11Symbols::getInstance()->xUnmapWindow (getDisplay(), client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Window getParentX11Window()
|
||||
{
|
||||
if (auto* peer = owner.getPeer())
|
||||
return reinterpret_cast<Window> (peer->getNativeHandle());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Display* getDisplay() { return XWindowSystem::getInstance()->getDisplay(); }
|
||||
|
||||
//==============================================================================
|
||||
bool getXEmbedMappedFlag()
|
||||
{
|
||||
XWindowSystemUtilities::GetXProperty embedInfo (getDisplay(), client, infoAtom, 0, 2, false, infoAtom);
|
||||
|
||||
if (embedInfo.success && embedInfo.actualFormat == 32
|
||||
&& embedInfo.numItems >= 2 && embedInfo.data != nullptr)
|
||||
{
|
||||
long version;
|
||||
memcpy (&version, embedInfo.data, sizeof (long));
|
||||
|
||||
supportsXembed = true;
|
||||
xembedVersion = jmin ((int) maxXEmbedVersionToSupport, (int) version);
|
||||
|
||||
long flags;
|
||||
memcpy (&flags, embedInfo.data + sizeof (long), sizeof (long));
|
||||
|
||||
return ((flags & XEMBED_MAPPED) != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
supportsXembed = false;
|
||||
xembedVersion = maxXEmbedVersionToSupport;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void propertyChanged (const Atom& a)
|
||||
{
|
||||
if (a == infoAtom)
|
||||
updateMapping();
|
||||
}
|
||||
|
||||
void configureNotify()
|
||||
{
|
||||
XWindowAttributes attr;
|
||||
auto dpy = getDisplay();
|
||||
|
||||
if (X11Symbols::getInstance()->xGetWindowAttributes (dpy, client, &attr))
|
||||
{
|
||||
XWindowAttributes hostAttr;
|
||||
|
||||
if (X11Symbols::getInstance()->xGetWindowAttributes (dpy, host, &hostAttr))
|
||||
if (attr.width != hostAttr.width || attr.height != hostAttr.height)
|
||||
X11Symbols::getInstance()->xResizeWindow (dpy, host, (unsigned int) attr.width, (unsigned int) attr.height);
|
||||
|
||||
// as the client window is not on any screen yet, we need to guess
|
||||
// on which screen it might appear to get a scaling factor :-(
|
||||
auto& displays = Desktop::getInstance().getDisplays();
|
||||
auto* peer = owner.getPeer();
|
||||
const double scale = (peer != nullptr ? peer->getPlatformScaleFactor()
|
||||
: displays.getPrimaryDisplay()->scale);
|
||||
|
||||
Point<int> topLeftInPeer
|
||||
= (peer != nullptr ? peer->getComponent().getLocalPoint (&owner, Point<int> (0, 0))
|
||||
: owner.getBounds().getTopLeft());
|
||||
|
||||
Rectangle<int> newBounds (topLeftInPeer.getX(), topLeftInPeer.getY(),
|
||||
static_cast<int> (static_cast<double> (attr.width) / scale),
|
||||
static_cast<int> (static_cast<double> (attr.height) / scale));
|
||||
|
||||
|
||||
if (peer != nullptr)
|
||||
newBounds = owner.getLocalArea (&peer->getComponent(), newBounds);
|
||||
|
||||
jassert (newBounds.getX() == 0 && newBounds.getY() == 0);
|
||||
|
||||
if (newBounds != owner.getLocalBounds())
|
||||
owner.setSize (newBounds.getWidth(), newBounds.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
void peerChanged (ComponentPeer* newPeer)
|
||||
{
|
||||
if (newPeer != lastPeer)
|
||||
{
|
||||
if (lastPeer != nullptr)
|
||||
keyWindow = nullptr;
|
||||
|
||||
auto dpy = getDisplay();
|
||||
Window rootWindow = X11Symbols::getInstance()->xRootWindow (dpy, DefaultScreen (dpy));
|
||||
Rectangle<int> newBounds = getX11BoundsFromJuce();
|
||||
|
||||
if (newPeer == nullptr)
|
||||
X11Symbols::getInstance()->xUnmapWindow (dpy, host);
|
||||
|
||||
Window newParent = (newPeer != nullptr ? getParentX11Window() : rootWindow);
|
||||
X11Symbols::getInstance()->xReparentWindow (dpy, host, newParent, newBounds.getX(), newBounds.getY());
|
||||
|
||||
lastPeer = newPeer;
|
||||
|
||||
if (newPeer != nullptr)
|
||||
{
|
||||
if (wantsFocus)
|
||||
{
|
||||
keyWindow = SharedKeyWindow::getKeyWindowForPeer (newPeer);
|
||||
updateKeyFocus();
|
||||
}
|
||||
|
||||
componentMovedOrResized (owner, true, true);
|
||||
X11Symbols::getInstance()->xMapWindow (dpy, host);
|
||||
|
||||
broughtToFront();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateKeyFocus()
|
||||
{
|
||||
if (lastPeer != nullptr && lastPeer->isFocused())
|
||||
X11Symbols::getInstance()->xSetInputFocus (getDisplay(), getCurrentFocusWindow (lastPeer), RevertToParent, CurrentTime);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void handleXembedCmd (const ::Time& /*xTime*/, long opcode, long /*detail*/, long /*data1*/, long /*data2*/)
|
||||
{
|
||||
if (auto* peer = owner.getPeer())
|
||||
peer->getCurrentModifiersRealtime();
|
||||
|
||||
switch (opcode)
|
||||
{
|
||||
case XEMBED_REQUEST_FOCUS:
|
||||
if (wantsFocus)
|
||||
owner.grabKeyboardFocus();
|
||||
break;
|
||||
|
||||
case XEMBED_FOCUS_NEXT:
|
||||
if (wantsFocus)
|
||||
owner.moveKeyboardFocusToSibling (true);
|
||||
break;
|
||||
|
||||
case XEMBED_FOCUS_PREV:
|
||||
if (wantsFocus)
|
||||
owner.moveKeyboardFocusToSibling (false);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool handleX11Event (const XEvent& e)
|
||||
{
|
||||
if (e.xany.window == client && client != 0)
|
||||
{
|
||||
switch (e.type)
|
||||
{
|
||||
case PropertyNotify:
|
||||
propertyChanged (e.xproperty.atom);
|
||||
return true;
|
||||
|
||||
case ConfigureNotify:
|
||||
if (allowResize)
|
||||
configureNotify();
|
||||
else
|
||||
MessageManager::callAsync ([this] {componentMovedOrResized (owner, true, true);});
|
||||
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (e.xany.window == host && host != 0)
|
||||
{
|
||||
switch (e.type)
|
||||
{
|
||||
case ReparentNotify:
|
||||
if (e.xreparent.parent == host && e.xreparent.window != client)
|
||||
{
|
||||
setClient (e.xreparent.window, false);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case CreateNotify:
|
||||
if (e.xcreatewindow.parent != e.xcreatewindow.window && e.xcreatewindow.parent == host && e.xcreatewindow.window != client)
|
||||
{
|
||||
setClient (e.xcreatewindow.window, false);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case GravityNotify:
|
||||
componentMovedOrResized (owner, true, true);
|
||||
return true;
|
||||
|
||||
case ClientMessage:
|
||||
if (e.xclient.message_type == messageTypeAtom && e.xclient.format == 32)
|
||||
{
|
||||
handleXembedCmd ((::Time) e.xclient.data.l[0], e.xclient.data.l[1],
|
||||
e.xclient.data.l[2], e.xclient.data.l[3],
|
||||
e.xclient.data.l[4]);
|
||||
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void sendXEmbedEvent (const ::Time& xTime, long opcode,
|
||||
long opcodeMinor = 0, long data1 = 0, long data2 = 0)
|
||||
{
|
||||
XClientMessageEvent msg;
|
||||
auto dpy = getDisplay();
|
||||
|
||||
::memset (&msg, 0, sizeof (XClientMessageEvent));
|
||||
msg.window = client;
|
||||
msg.type = ClientMessage;
|
||||
msg.message_type = messageTypeAtom;
|
||||
msg.format = 32;
|
||||
msg.data.l[0] = (long) xTime;
|
||||
msg.data.l[1] = opcode;
|
||||
msg.data.l[2] = opcodeMinor;
|
||||
msg.data.l[3] = data1;
|
||||
msg.data.l[4] = data2;
|
||||
|
||||
X11Symbols::getInstance()->xSendEvent (dpy, client, False, NoEventMask, (XEvent*) &msg);
|
||||
X11Symbols::getInstance()->xSync (dpy, False);
|
||||
}
|
||||
|
||||
Rectangle<int> getX11BoundsFromJuce()
|
||||
{
|
||||
if (auto* peer = owner.getPeer())
|
||||
{
|
||||
auto r = peer->getComponent().getLocalArea (&owner, owner.getLocalBounds());
|
||||
return r * peer->getPlatformScaleFactor() * peer->getComponent().getDesktopScaleFactor();
|
||||
}
|
||||
|
||||
return owner.getLocalBounds();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
friend bool juce::juce_handleXEmbedEvent (ComponentPeer*, void*);
|
||||
friend unsigned long juce::juce_getCurrentFocusWindow (ComponentPeer*);
|
||||
|
||||
static Array<Pimpl*>& getWidgets()
|
||||
{
|
||||
static Array<Pimpl*> i;
|
||||
return i;
|
||||
}
|
||||
|
||||
static bool dispatchX11Event (ComponentPeer* p, const XEvent* eventArg)
|
||||
{
|
||||
if (eventArg != nullptr)
|
||||
{
|
||||
auto& e = *eventArg;
|
||||
|
||||
if (auto w = e.xany.window)
|
||||
for (auto* widget : getWidgets())
|
||||
if (w == widget->host || w == widget->client)
|
||||
return widget->handleX11Event (e);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto* widget : getWidgets())
|
||||
if (widget->owner.getPeer() == p)
|
||||
widget->peerChanged (nullptr);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static Window getCurrentFocusWindow (ComponentPeer* p)
|
||||
{
|
||||
if (p != nullptr)
|
||||
{
|
||||
for (auto* widget : getWidgets())
|
||||
if (widget->owner.getPeer() == p && widget->owner.hasKeyboardFocus (false))
|
||||
return widget->client;
|
||||
}
|
||||
|
||||
return SharedKeyWindow::getCurrentFocusWindow (p);
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
XEmbedComponent::XEmbedComponent (bool wantsKeyboardFocus, bool allowForeignWidgetToResizeComponent)
|
||||
: pimpl (new Pimpl (*this, 0, wantsKeyboardFocus, false, allowForeignWidgetToResizeComponent))
|
||||
{
|
||||
setOpaque (true);
|
||||
}
|
||||
|
||||
XEmbedComponent::XEmbedComponent (unsigned long wID, bool wantsKeyboardFocus, bool allowForeignWidgetToResizeComponent)
|
||||
: pimpl (new Pimpl (*this, wID, wantsKeyboardFocus, true, allowForeignWidgetToResizeComponent))
|
||||
{
|
||||
setOpaque (true);
|
||||
}
|
||||
|
||||
XEmbedComponent::~XEmbedComponent() {}
|
||||
|
||||
void XEmbedComponent::paint (Graphics& g)
|
||||
{
|
||||
g.fillAll (Colours::lightgrey);
|
||||
}
|
||||
|
||||
void XEmbedComponent::focusGainedWithDirection (FocusChangeType changeType, FocusChangeDirection direction)
|
||||
{
|
||||
pimpl->focusGained (changeType, direction);
|
||||
}
|
||||
|
||||
void XEmbedComponent::focusLost (FocusChangeType changeType) { pimpl->focusLost (changeType); }
|
||||
void XEmbedComponent::broughtToFront() { pimpl->broughtToFront(); }
|
||||
unsigned long XEmbedComponent::getHostWindowID() { return pimpl->getHostWindowID(); }
|
||||
void XEmbedComponent::removeClient() { pimpl->setClient (0, true); }
|
||||
void XEmbedComponent::updateEmbeddedBounds() { pimpl->updateEmbeddedBounds(); }
|
||||
|
||||
//==============================================================================
|
||||
bool juce_handleXEmbedEvent (ComponentPeer* p, void* e)
|
||||
{
|
||||
return XEmbedComponent::Pimpl::dispatchX11Event (p, reinterpret_cast<const XEvent*> (e));
|
||||
}
|
||||
|
||||
unsigned long juce_getCurrentFocusWindow (ComponentPeer* peer)
|
||||
{
|
||||
return (unsigned long) XEmbedComponent::Pimpl::getCurrentFocusWindow (peer);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
Reference in New Issue
Block a user